diff --git a/backend/controllers/socialnetworkController.js b/backend/controllers/socialnetworkController.js index 223ba72..4bc1d8e 100644 --- a/backend/controllers/socialnetworkController.js +++ b/backend/controllers/socialnetworkController.js @@ -9,6 +9,8 @@ class SocialNetworkController { this.getFolders = this.getFolders.bind(this); this.uploadImage = this.uploadImage.bind(this); this.getImage = this.getImage.bind(this); + this.getImageVisibilityTypes = this.getImageVisibilityTypes.bind(this); + this.getFolderImageList = this.getFolderImageList.bind(this); } async userSearch(req, res) { @@ -39,8 +41,9 @@ class SocialNetworkController { async createFolder(req, res) { try { + const userId = req.headers.userid; const folderData = req.body; - const folder = await this.socialNetworkService.createFolder(folderData); + const folder = await this.socialNetworkService.createFolder(userId, folderData); res.status(201).json(folder); } catch (error) { console.error('Error in createFolder:', error); @@ -59,10 +62,23 @@ class SocialNetworkController { } } + async getFolderImageList(req, res) { + try { + const userId = req.headers.userid; + const { folderId } = req.params; + const images = await this.socialNetworkService.getFolderImageList(userId, folderId); + res.status(200).json(images); + } catch (error) { + console.error('Error in getFolderImageList:', error); + res.status(500).json({ error: error.message }); + } + } + async uploadImage(req, res) { try { + const userId = req.headers.userid; const imageData = req.body; - const image = await this.socialNetworkService.uploadImage(imageData); + const image = await this.socialNetworkService.uploadImage(userId, imageData); res.status(201).json(image); } catch (error) { console.error('Error in uploadImage:', error); @@ -80,6 +96,16 @@ class SocialNetworkController { res.status(500).json({ error: error.message }); } } + + async getImageVisibilityTypes(req, res) { + try { + const types = await this.socialNetworkService.getPossibleImageVisibilities(); + res.status(200).json(types); + } catch (error) { + console.log(error); + res.status(500).json({ error: error.message }); + } + } } export default SocialNetworkController; diff --git a/backend/models/associations.js b/backend/models/associations.js index e0b9cbe..5a94d26 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -12,6 +12,11 @@ import UserParamVisibilityType from './type/user_param_visibility.js'; import UserParamVisibility from './community/user_param_visibility.js'; import Folder from './community/folder.js'; import Image from './community/image.js'; +import ImageVisibilityType from './type/image_visibility.js'; +import ImageVisibilityUser from './community/image_visibility_user.js'; +import FolderImageVisibility from './community/folder_image_visibility.js'; +import ImageImageVisibility from './community/image_image_visibility.js'; +import FolderVisibilityUser from './community/folder_visibility_user.js'; export default function setupAssociations() { SettingsType.hasMany(UserParamType, { foreignKey: 'settingsId', as: 'user_param_types' }); @@ -57,4 +62,37 @@ export default function setupAssociations() { Image.belongsTo(User, { foreignKey: 'userId' }); User.hasMany(Image, { foreignKey: 'userId' }); + + Folder.belongsToMany(ImageVisibilityType, { + through: FolderImageVisibility, + foreignKey: 'folderId', + otherKey: 'visibilityTypeId' + }); + ImageVisibilityType.belongsToMany(Folder, { + through: FolderImageVisibility, + foreignKey: 'visibilityTypeId', + otherKey: 'folderId' + }); + + Image.belongsToMany(ImageVisibilityType, { + through: ImageImageVisibility, + foreignKey: 'imageId', + otherKey: 'visibilityTypeId' + }); + ImageVisibilityType.belongsToMany(Image, { + through: ImageImageVisibility, + foreignKey: 'visibilityTypeId', + otherKey: 'imageId' + }); + + Folder.belongsToMany(ImageVisibilityUser, { + through: FolderVisibilityUser, + foreignKey: 'folderId', + otherKey: 'visibilityUserId' + }); + ImageVisibilityUser.belongsToMany(Folder, { + through: FolderVisibilityUser, + foreignKey: 'visibilityUserId', + otherKey: 'folderId' + }); } diff --git a/backend/models/community/folder.js b/backend/models/community/folder.js index 798ab5b..202de60 100644 --- a/backend/models/community/folder.js +++ b/backend/models/community/folder.js @@ -23,14 +23,6 @@ const Folder = sequelize.define('folder', { key: 'id', }, }, - visibilityType: { - type: DataTypes.INTEGER, - allowNull: false, - references: { - model: UserParamVisibilityType, - key: 'id', - }, - }, }, { tableName: 'folder', schema: 'community', diff --git a/backend/models/community/folder_image_visibility.js b/backend/models/community/folder_image_visibility.js new file mode 100644 index 0000000..0ade275 --- /dev/null +++ b/backend/models/community/folder_image_visibility.js @@ -0,0 +1,37 @@ +import { sequelize } from '../../utils/sequelize.js'; +import { DataTypes } from 'sequelize'; + +const FolderImageVisibility = sequelize.define('folder_image_visibility', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + folderId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'folder', + key: 'id' + } + }, + visibilityTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: { + schema: 'type', + tableName: 'image_visibility_type' + }, + key: 'id' + } + } +}, { + tableName: 'folder_image_visibility', + schema: 'community', + timestamps: false, + underscored: true, +}); + +export default FolderImageVisibility; diff --git a/backend/models/community/folder_visibility_user.js b/backend/models/community/folder_visibility_user.js new file mode 100644 index 0000000..82d0561 --- /dev/null +++ b/backend/models/community/folder_visibility_user.js @@ -0,0 +1,34 @@ +import { sequelize } from '../../utils/sequelize.js'; +import { DataTypes } from 'sequelize'; + +const FolderVisibilityUser = sequelize.define('folder_visibility_user', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + folderId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'folder', + key: 'id' + } + }, + visibilityUserId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'image_visibility_user', + key: 'id' + } + } +}, { + tableName: 'folder_visibility_user', + schema: 'community', + timestamps: false, + underscored: true, +}); + +export default FolderVisibilityUser; diff --git a/backend/models/community/image.js b/backend/models/community/image.js index 879486a..2a768bf 100644 --- a/backend/models/community/image.js +++ b/backend/models/community/image.js @@ -36,14 +36,6 @@ const Image = sequelize.define('image', { key: 'id', }, }, - visibilityType: { - type: DataTypes.INTEGER, - allowNull: false, - references: { - model: UserParamVisibilityType, - key: 'id', - }, - }, }, { tableName: 'image', schema: 'community', diff --git a/backend/models/community/image_image_visibility.js b/backend/models/community/image_image_visibility.js new file mode 100644 index 0000000..ac22561 --- /dev/null +++ b/backend/models/community/image_image_visibility.js @@ -0,0 +1,37 @@ +import { sequelize } from '../../utils/sequelize.js'; +import { DataTypes } from 'sequelize'; + +const ImageImageVisibility = sequelize.define('image_image_visibility', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + imageId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'image', + key: 'id' + } + }, + visibilityTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: { + schema: 'type', + tableName: 'image_visibility_type' + }, + key: 'id' + } + } +}, { + tableName: 'image_image_visibility', + schema: 'community', + timestamps: false, + underscored: true, +}); + +export default ImageImageVisibility; diff --git a/backend/models/community/image_visibility_user.js b/backend/models/community/image_visibility_user.js new file mode 100644 index 0000000..58a2564 --- /dev/null +++ b/backend/models/community/image_visibility_user.js @@ -0,0 +1,26 @@ +import { sequelize } from '../../utils/sequelize.js'; +import { DataTypes } from 'sequelize'; + +const ImageVisibilityUser = sequelize.define('image_visibility_user', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + image_id: { + type: DataTypes.INTEGER, + allowNull: false + }, + user_id: { + type: DataTypes.INTEGER, + allowNull: false + } +}, { + tableName: 'image_visibility_user', + timestamps: false, + underscored: true, + schema: 'community' +}); + +export default ImageVisibilityUser; diff --git a/backend/models/index.js b/backend/models/index.js index fc270b3..0e2d576 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -14,6 +14,11 @@ import UserParamVisibilityType from './type/user_param_visibility.js'; import UserParamVisibility from './community/user_param_visibility.js'; import Folder from './community/folder.js'; import Image from './community/image.js'; +import ImageVisibilityType from './type/image_visibility.js'; +import ImageVisibilityUser from './community/image_visibility_user.js'; +import FolderImageVisibility from './community/folder_image_visibility.js'; +import ImageImageVisibility from './community/image_image_visibility.js'; +import FolderVisibilityUser from './community/folder_visibility_user.js'; const models = { SettingsType, @@ -25,13 +30,18 @@ const models = { Login, UserRight, InterestType, - InterestTranslationType, + InterestTranslationType, Interest, ContactMessage, UserParamVisibilityType, UserParamVisibility, Folder, Image, + ImageVisibilityType, + ImageVisibilityUser, + FolderImageVisibility, + ImageImageVisibility, + FolderVisibilityUser, }; export default models; diff --git a/backend/models/type/image_visibility.js b/backend/models/type/image_visibility.js new file mode 100644 index 0000000..c34d526 --- /dev/null +++ b/backend/models/type/image_visibility.js @@ -0,0 +1,22 @@ +import { sequelize } from '../../utils/sequelize.js'; +import { DataTypes } from 'sequelize'; + +const ImageVisibilityType = sequelize.define('image_visibility_type', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + description: { + type: DataTypes.STRING, + allowNull: false + } +}, { + tableName: 'image_visibility_type', + schema: 'type', + timestamps: false, + underscored: true, +}); + +export default ImageVisibilityType; diff --git a/backend/routers/socialnetworkRouter.js b/backend/routers/socialnetworkRouter.js index f7fe174..6bb5e71 100644 --- a/backend/routers/socialnetworkRouter.js +++ b/backend/routers/socialnetworkRouter.js @@ -5,13 +5,13 @@ import SocialNetworkController from '../controllers/socialnetworkController.js'; const router = express.Router(); const socialNetworkController = new SocialNetworkController(); -router.post('/usersearch', authenticate, socialNetworkController.userSearch); -router.get('/profile/:userId', authenticate, socialNetworkController.profile); router.post('/usersearch', authenticate, socialNetworkController.userSearch); router.get('/profile/:userId', authenticate, socialNetworkController.profile); router.post('/folders', authenticate, socialNetworkController.createFolder); router.get('/folders', authenticate, socialNetworkController.getFolders); +router.get('/folder/:folderId', authenticate, socialNetworkController.getFolderImageList); router.post('/images', authenticate, socialNetworkController.uploadImage); router.get('/images/:imageId', authenticate, socialNetworkController.getImage); +router.get('/imagevisibilities', authenticate, socialNetworkController.getImageVisibilityTypes); export default router; diff --git a/backend/services/socialnetworkService.js b/backend/services/socialnetworkService.js index 73b52c7..52b2a49 100644 --- a/backend/services/socialnetworkService.js +++ b/backend/services/socialnetworkService.js @@ -8,6 +8,8 @@ import UserParamVisibility from '../models/community/user_param_visibility.js'; import UserParamVisibilityType from '../models/type/user_param_visibility.js'; import Folder from '../models/community/folder.js'; import Image from '../models/community/image.js'; +import ImageVisibilityType from '../models/type/image_visibility.js'; +import FolderImageVisibility from '../models/community/folder_image_visibility.js'; class SocialNetworkService extends BaseService { async searchUsers({ username, ageFrom, ageTo, genders }) { @@ -23,20 +25,84 @@ class SocialNetworkService extends BaseService { return this.constructUserProfile(user, requestingUserId); } - async createFolder(data) { - this.validateFolderData(data); - await this.checkUserAccess(data.userId); - return await Folder.create(data); + async createFolder(hashedUserId, data) { + await this.checkUserAccess(hashedUserId); + const user = await User.findOne({ + hashedId: hashedUserId + }); + const parentFolder = Folder.findOne({ + id: data.parentId, + userId: user.id + }); + if (!parentFolder) { + throw new Error('foldernotfound'); + } + const newFolder = await Folder.create({ + parentId: data.parentId, + userId: user.id, + name: data.name + }); + for (const visibilityId of data.visibilities) { + await FolderImageVisibility.create({ + folderId: newFolder.id, + visibilityTypeId: visibilityId + }); + } + return newFolder; } - async getFolders(userId) { - await this.checkUserAccess(userId); - return await Folder.findAll({ where: { userId } }); + async getFolders(hashedId) { + const userId = await this.checkUserAccess(hashedId); + let rootFolder = await Folder.findOne({ where: { parentId: null, userId } }); + if (!rootFolder) { + const user = await User.findOne({ where: { id: userId } }); + const visibility = await ImageVisibilityType.findOne({ + where: { + description: 'everyone' + } + }); + rootFolder = await Folder.create({ + name: user.username, + parentId: null, + userId, + visibilityTypeId: visibility.id + }); + } + const children = await this.getSubFolders(rootFolder.id, userId); + rootFolder = rootFolder.get(); + rootFolder.children = children; + return rootFolder; } - async uploadImage(imageData) { + async getSubFolders(parentId, userId) { + const folders = await Folder.findAll({ where: { parentId, userId } }); + for (const folder of folders) { + const children = await this.getSubFolders(folder.id, userId); + folder.setDataValue('children', children); + } + return folders.map(folder => folder.get()); + } + + async getFolderImageList(hashedId, folderId) { + const userId = await this.checkUserAccess(hashedId); + const folder = await Folder.findOne({ + where: { + id: folderId, + userId + } + }); + if (!folder) throw new Error('Folder not found'); + return await Image.findAll({ + where: { + folderId: folder.id + } + }); + } + + async uploadImage(hashedId, imageData) { this.validateImageData(imageData); - await this.checkUserAccess(imageData.userId); + const userId = await this.checkUserAccess(hashedId); + imageData.id = userId; return await Image.create(imageData); } @@ -47,9 +113,10 @@ class SocialNetworkService extends BaseService { return image; } - async checkUserAccess(userId) { - const user = await User.findByPk(userId); + async checkUserAccess(hashedId) { + const user = await User.findOne({ hashedId: hashedId }); if (!user || !user.active) throw new Error('Access denied: User not found or inactive'); + return user.id; } validateFolderData(data) { @@ -184,6 +251,10 @@ class SocialNetworkService extends BaseService { }); return userParamValue ? userParamValue.value : value; } + + async getPossibleImageVisibilities() { + return await ImageVisibilityType.findAll(); + } } export default SocialNetworkService; diff --git a/backend/utils/initializeImageTypes.js b/backend/utils/initializeImageTypes.js new file mode 100644 index 0000000..dc85ac6 --- /dev/null +++ b/backend/utils/initializeImageTypes.js @@ -0,0 +1,19 @@ +import ImageVisibilityType from '../models/type/image_visibility.js'; + +const initializeImageTypes = async () => { + const visibilities = [ + 'everyone', + 'friends', + 'adults', + 'friends-and-adults', + 'selected-users', + ]; + for (const visibility of visibilities) { + await ImageVisibilityType.findOrCreate({ + where: { description: visibility }, + defaults: { description: visibility } + }); + } +} + +export default initializeImageTypes; diff --git a/backend/utils/syncDatabase.js b/backend/utils/syncDatabase.js index 0f14f1d..e03b681 100644 --- a/backend/utils/syncDatabase.js +++ b/backend/utils/syncDatabase.js @@ -2,6 +2,7 @@ import { initializeDatabase } from './sequelize.js'; import initializeTypes from './initializeTypes.js'; import initializeSettings from './initializeSettings.js'; import initializeUserRights from './initializeUserRights.js'; +import initializeImageTypes from './initializeImageTypes.js'; import setupAssociations from '../models/associations.js'; import models from '../models/index.js'; import { createTriggers } from '../models/trigger.js'; @@ -18,6 +19,7 @@ const syncDatabase = async () => { await initializeSettings(); await initializeTypes(); await initializeUserRights(); + await initializeImageTypes(); } catch (error) { console.error('Unable to synchronize the database:', error); } diff --git a/frontend/public/images/icons/folder.png b/frontend/public/images/icons/folder.png index 9f8c935..ed0114a 100644 Binary files a/frontend/public/images/icons/folder.png and b/frontend/public/images/icons/folder.png differ diff --git a/frontend/public/images/icons/folder16.png b/frontend/public/images/icons/folder16.png new file mode 100644 index 0000000..f3cb6d2 Binary files /dev/null and b/frontend/public/images/icons/folder16.png differ diff --git a/frontend/public/images/icons/folder24.png b/frontend/public/images/icons/folder24.png new file mode 100644 index 0000000..feded4a Binary files /dev/null and b/frontend/public/images/icons/folder24.png differ diff --git a/frontend/src/components/FolderItem.vue b/frontend/src/components/FolderItem.vue new file mode 100644 index 0000000..b1d54e1 --- /dev/null +++ b/frontend/src/components/FolderItem.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/frontend/src/components/form/MultiselectWidget.vue b/frontend/src/components/form/MultiselectWidget.vue index f8f5bdf..13f0d29 100644 --- a/frontend/src/components/form/MultiselectWidget.vue +++ b/frontend/src/components/form/MultiselectWidget.vue @@ -12,10 +12,10 @@ :track-by="'value'" >