Files
yourpart3/backend/services/socialnetworkService.js

380 lines
14 KiB
JavaScript

import BaseService from './BaseService.js';
import { Op } from 'sequelize';
import User from '../models/community/user.js';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import UserParamValue from '../models/type/user_param_value.js';
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';
import ImageImageVisibility from '../models/community/image_image_visibility.js';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class SocialNetworkService extends BaseService {
async searchUsers({ username, ageFrom, ageTo, genders }) {
const whereClause = this.buildSearchWhereClause(username);
const users = await User.findAll({ where: whereClause, include: this.getUserParamsInclude() });
return this.filterUsersByCriteria(users, ageFrom, ageTo, genders);
}
async getProfile(hashedUserId, requestingUserId) {
await this.checkUserAccess(requestingUserId);
const user = await this.fetchUserProfile(hashedUserId);
if (!user) return null;
return this.constructUserProfile(user, requestingUserId);
}
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(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 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
},
order: [
['title', 'asc']
]
});
}
async uploadImage(hashedId, file, formData) {
const userId = await this.getUserId(hashedId);
const newFileName = this.generateUniqueFileName(file.originalname);
const filePath = this.buildFilePath(newFileName);
await this.saveFile(file.buffer, filePath);
const newImage = await this.createImageRecord(formData, userId, file, newFileName);
await this.saveImageVisibilities(newImage.id, formData.visibility);
return newImage;
}
async getUserId(hashedId) {
return await this.checkUserAccess(hashedId);
}
generateUniqueFileName(originalFileName) {
const uniqueHash = uuidv4();
return `${uniqueHash}`;
}
buildFilePath(fileName) {
const userImagesPath = path.join(__dirname, '../images/user');
return path.join(userImagesPath, fileName);
}
async saveFile(buffer, filePath) {
try {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, buffer);
} catch (error) {
throw new Error(`Failed to save file: ${error.message}`);
}
}
async createImageRecord(formData, userId, file, fileName) {
try {
return await Image.create({
title: formData.title,
description: formData.description || null,
originalFileName: file.originalname,
hash: fileName,
folderId: formData.folderId,
userId: userId,
});
} catch (error) {
throw new Error(`Failed to create image record: ${error.message}`);
}
}
async saveImageVisibilities(imageId, visibilities) {
if (typeof visibilities === 'string') {
visibilities = JSON.parse(visibilities);
}
if (!visibilities || !Array.isArray(visibilities)) {
throw new Error('Invalid visibilities provided');
}
try {
const visibilityPromises = visibilities.map(visibilityId => {
return ImageImageVisibility.create({
imageId: imageId,
visibilityTypeId: visibilityId
});
});
await Promise.all(visibilityPromises);
} catch (error) {
throw new Error(`Failed to save image visibilities: ${error.message}`);
}
}
async getImage(imageId) {
const image = await Image.findByPk(imageId);
if (!image) throw new Error('Image not found');
await this.checkUserAccess(image.userId);
return image;
}
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) {
if (!data.name || typeof data.name !== 'string') throw new Error('Invalid folder data: Name is required');
}
validateImageData(imageData) {
if (!imageData.url || typeof imageData.url !== 'string') throw new Error('Invalid image data: URL is required');
}
buildSearchWhereClause(username) {
const whereClause = { active: true, searchable: true };
if (username) {
whereClause.username = { [Op.iLike]: `%${username}%` };
}
return whereClause;
}
getUserParamsInclude() {
return [
{
model: UserParam,
as: 'user_params',
include: [{ model: UserParamType, as: 'paramType', required: true }]
}
];
}
filterUsersByCriteria(users, ageFrom, ageTo, genders) {
const results = [];
for (const user of users) {
const userDetails = this.extractUserDetails(user);
if (this.isUserValid(userDetails, ageFrom, ageTo, genders)) {
results.push(userDetails);
}
}
return results;
}
extractUserDetails(user) {
const birthdateParam = user.user_params.find(param => param.paramType.description === 'birthdate');
const genderParam = user.user_params.find(param => param.paramType.description === 'gender');
const age = birthdateParam ? this.calculateAge(birthdateParam.value) : null;
const gender = genderParam ? this.getGenderValue(genderParam.value) : null;
return {
id: user.hashedId,
username: user.username,
email: user.email,
gender,
age
};
}
async getGenderValue(genderId) {
const genderValue = await UserParamValue.findOne({ where: { id: genderId } });
return genderValue ? genderValue.value : null;
}
isUserValid(userDetails, ageFrom, ageTo, genders) {
const { age, gender } = userDetails;
const isWithinAgeRange = (!ageFrom || age >= ageFrom) && (!ageTo || age <= ageTo);
const isGenderValid = !genders || !genders.length || (gender && genders.includes(gender));
return isWithinAgeRange && isGenderValid && age >= 14;
}
async fetchUserProfile(hashedUserId) {
return await User.findOne({
where: { hashedId: hashedUserId, active: true, searchable: true },
include: [
{
model: UserParam,
as: 'user_params',
include: [
{ model: UserParamType, as: 'paramType' },
{ model: UserParamVisibility, as: 'param_visibilities', include: [{ model: UserParamVisibilityType, as: 'visibility_type' }] }
],
order: [['order_id', 'asc']]
}
]
});
}
async constructUserProfile(user, requestingUserId) {
const userParams = {};
const requestingUserAge = await this.getUserAge(requestingUserId);
for (const param of user.user_params) {
const visibility = param.param_visibilities?.[0]?.visibility_type?.description || 'Invisible';
if (visibility === 'Invisible') continue;
if (this.isVisibleToUser(visibility, requestingUserAge)) {
userParams[param.paramType.description] = {
type: param.paramType.datatype,
value: await this.getParamValue(param)
};
}
}
return {
username: user.username,
registrationDate: user.registrationDate,
params: userParams
};
}
async getUserAge(userId) {
const params = await this.getUserParams(userId, ['birthdate']);
const birthdateParam = params.find(param => param.paramType.description === 'birthdate');
return birthdateParam ? this.calculateAge(birthdateParam.value) : 0;
}
isVisibleToUser(visibility, requestingUserAge) {
return visibility === 'All' ||
(visibility === 'FriendsAndAdults' && requestingUserAge >= 18) ||
(visibility === 'AdultsOnly' && requestingUserAge >= 18);
}
async getParamValue(param) {
let paramValue = param.value;
try {
const parsedValue = JSON.parse(paramValue);
if (Array.isArray(parsedValue)) {
paramValue = await Promise.all(parsedValue.map(value => this.getValueFromDatabase(value, param.paramTypeId)));
} else if (/^\d+$/.test(paramValue)) {
paramValue = await this.getValueFromDatabase(paramValue, param.paramTypeId);
}
} catch (e) {
}
return paramValue;
}
async getValueFromDatabase(value, paramTypeId) {
const userParamValue = await UserParamValue.findOne({
where: { id: parseInt(value, 10), userParamTypeId: paramTypeId }
});
return userParamValue ? userParamValue.value : value;
}
async getPossibleImageVisibilities() {
return await ImageVisibilityType.findAll();
}
async getImageFilePath(hashedUserId, hash) {
const image = await Image.findOne({ where: { hash } });
if (!image) {
throw new Error('Image not found');
}
const userId = await this.checkUserAccess(hashedUserId);
const hasAccess = await this.checkUserImageAccess(userId, image.id);
if (!hasAccess) {
throw new Error('Access denied');
}
const imagePath = this.buildFilePath(image.hash);
if (!fs.existsSync(imagePath)) {
throw new Error('File not found');
}
return imagePath;
}
async checkUserImageAccess(userId, imageId) {
const image = await Image.findByPk(imageId);
if (image.userId === userId) {
return true;
}
const accessRules = await ImageImageVisibility.findAll({
where: { imageId }
});
return accessRules.some(rule => {
return false;
});
}
async changeImage(hashedUserId, imageId, title, visibilities) {
const userId = await this.checkUserAccess(hashedUserId);
await this.checkUserImageAccess(userId, imageId);
const image = await Image.findOne({ where: { id: imageId } });
if (!image) {
throw new Error('image not found')
}
await image.update({ title: title });
await ImageImageVisibility.destroy({ where: { imageId } });
for (const visibility of visibilities) {
await ImageImageVisibility.create({ imageId, visibilityTypeId: visibility.id });
}
return image.folderId;
}
}
export default SocialNetworkService;