feat(socialnetwork): enhance folder and video management with user visibility options
- Added functionality to manage selected users for adult folders and erotic videos, allowing for more granular visibility control. - Introduced new endpoints and methods in the SocialNetworkController and SocialNetworkService to handle selected users. - Updated the frontend components to include input fields for selected users in CreateFolderDialog, EditImageDialog, and EroticPicturesView. - Enhanced the routing to support fetching erotic folders and videos by username, improving user experience in profile views.
This commit is contained in:
@@ -13,6 +13,8 @@ import EroticContentReport from '../models/community/erotic_content_report.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 EroticVideoImageVisibility from '../models/community/erotic_video_image_visibility.js';
|
||||
import EroticVideoVisibilityUser from '../models/community/erotic_video_visibility_user.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import fs from 'fs';
|
||||
import fsPromises from 'fs/promises';
|
||||
@@ -74,6 +76,372 @@ class SocialNetworkService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
parseSelectedUsers(selectedUsers) {
|
||||
if (!selectedUsers) return [];
|
||||
if (Array.isArray(selectedUsers)) {
|
||||
return selectedUsers.map(value => String(value || '').trim()).filter(Boolean);
|
||||
}
|
||||
if (typeof selectedUsers === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(selectedUsers);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map(value => String(value || '').trim()).filter(Boolean);
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback to comma-separated values below.
|
||||
}
|
||||
return selectedUsers
|
||||
.split(',')
|
||||
.map(value => value.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async getActiveFriendIds(userId) {
|
||||
const friendships = await Friendship.findAll({
|
||||
where: {
|
||||
accepted: true,
|
||||
denied: false,
|
||||
withdrawn: false,
|
||||
[Op.or]: [
|
||||
{ user1Id: userId },
|
||||
{ user2Id: userId }
|
||||
]
|
||||
}
|
||||
});
|
||||
return friendships.map(friendship => (
|
||||
friendship.user1Id === userId ? friendship.user2Id : friendship.user1Id
|
||||
));
|
||||
}
|
||||
|
||||
async areUsersFriends(userId, otherUserId) {
|
||||
if (!userId || !otherUserId) return false;
|
||||
const friendship = await Friendship.findOne({
|
||||
where: {
|
||||
accepted: true,
|
||||
denied: false,
|
||||
withdrawn: false,
|
||||
[Op.or]: [
|
||||
{ user1Id: userId, user2Id: otherUserId },
|
||||
{ user1Id: otherUserId, user2Id: userId }
|
||||
]
|
||||
}
|
||||
});
|
||||
return Boolean(friendship);
|
||||
}
|
||||
|
||||
async resolveSelectedUserIds(ownerId, selectedUsers, { adultOnly = false } = {}) {
|
||||
const usernames = [...new Set(this.parseSelectedUsers(selectedUsers))];
|
||||
if (!usernames.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const users = await User.findAll({
|
||||
where: {
|
||||
[Op.or]: usernames.map(username => ({
|
||||
username: {
|
||||
[Op.iLike]: username
|
||||
}
|
||||
}))
|
||||
},
|
||||
attributes: ['id', 'username']
|
||||
});
|
||||
|
||||
const matchedUsers = [];
|
||||
for (const requestedName of usernames) {
|
||||
const user = users.find(candidate => (
|
||||
String(candidate.username || '').toLowerCase() === requestedName.toLowerCase()
|
||||
));
|
||||
if (!user) {
|
||||
throw new Error(`User "${requestedName}" not found`);
|
||||
}
|
||||
if (user.id === ownerId) {
|
||||
continue;
|
||||
}
|
||||
if (adultOnly) {
|
||||
const access = await this.getAdultAccessState(user.id);
|
||||
if (!access.adultAccessEnabled) {
|
||||
throw new Error(`User "${user.username}" is not approved for the adult area`);
|
||||
}
|
||||
}
|
||||
matchedUsers.push(user.id);
|
||||
}
|
||||
|
||||
return [...new Set(matchedUsers)];
|
||||
}
|
||||
|
||||
async saveFolderSelectedUsers(folderId, selectedUsers, ownerId, { adultOnly = false } = {}) {
|
||||
await FolderVisibilityUser.destroy({ where: { folderId } });
|
||||
const selectedUserIds = await this.resolveSelectedUserIds(ownerId, selectedUsers, { adultOnly });
|
||||
for (const userId of selectedUserIds) {
|
||||
await FolderVisibilityUser.create({ folderId, visibilityUserId: userId });
|
||||
}
|
||||
}
|
||||
|
||||
async saveImageSelectedUsers(imageId, selectedUsers, ownerId, { adultOnly = false } = {}) {
|
||||
await ImageVisibilityUser.destroy({ where: { imageId } });
|
||||
const selectedUserIds = await this.resolveSelectedUserIds(ownerId, selectedUsers, { adultOnly });
|
||||
for (const userId of selectedUserIds) {
|
||||
await ImageVisibilityUser.create({ imageId, userId });
|
||||
}
|
||||
}
|
||||
|
||||
async getFolderSelectedUsernames(folderId) {
|
||||
const selectedUserLinks = await FolderVisibilityUser.findAll({ where: { folderId } });
|
||||
if (!selectedUserLinks.length) return [];
|
||||
const users = await User.findAll({
|
||||
where: { id: selectedUserLinks.map(link => link.visibilityUserId) },
|
||||
attributes: ['id', 'username']
|
||||
});
|
||||
return users.map(user => user.username).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
async getImageSelectedUsernames(imageId) {
|
||||
const selectedUsers = await ImageVisibilityUser.findAll({ where: { imageId } });
|
||||
if (!selectedUsers.length) return [];
|
||||
const users = await User.findAll({
|
||||
where: { id: selectedUsers.map(entry => entry.userId) },
|
||||
attributes: ['id', 'username']
|
||||
});
|
||||
return users.map(user => user.username).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
async saveEroticVideoSelectedUsers(videoId, selectedUsers, ownerId, { adultOnly = false } = {}) {
|
||||
await EroticVideoVisibilityUser.destroy({ where: { eroticVideoId: videoId } });
|
||||
const selectedUserIds = await this.resolveSelectedUserIds(ownerId, selectedUsers, { adultOnly });
|
||||
for (const userId of selectedUserIds) {
|
||||
await EroticVideoVisibilityUser.create({ eroticVideoId: videoId, userId });
|
||||
}
|
||||
}
|
||||
|
||||
async getEroticVideoSelectedUsernames(videoId) {
|
||||
const selectedUsers = await EroticVideoVisibilityUser.findAll({ where: { eroticVideoId: videoId } });
|
||||
if (!selectedUsers.length) return [];
|
||||
const users = await User.findAll({
|
||||
where: { id: selectedUsers.map(entry => entry.userId) },
|
||||
attributes: ['id', 'username']
|
||||
});
|
||||
return users.map(user => user.username).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
async saveEroticVideoVisibilities(videoId, visibilities) {
|
||||
let normalizedVisibilities = visibilities;
|
||||
if (typeof normalizedVisibilities === 'string') {
|
||||
normalizedVisibilities = JSON.parse(normalizedVisibilities);
|
||||
}
|
||||
if (!Array.isArray(normalizedVisibilities) || !normalizedVisibilities.length) {
|
||||
throw new Error('Invalid visibilities provided');
|
||||
}
|
||||
|
||||
await EroticVideoImageVisibility.destroy({ where: { eroticVideoId: videoId } });
|
||||
for (const visibility of normalizedVisibilities) {
|
||||
const visibilityTypeId = typeof visibility === 'object' ? visibility.id : visibility;
|
||||
await EroticVideoImageVisibility.create({ eroticVideoId: videoId, visibilityTypeId });
|
||||
}
|
||||
}
|
||||
|
||||
async getEroticVideoVisibilityEntries(videoId) {
|
||||
return await ImageVisibilityType.findAll({
|
||||
include: [{
|
||||
model: EroticVideo,
|
||||
where: { id: videoId },
|
||||
attributes: [],
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
async enrichEroticVideoVisibilityMetadata(videos) {
|
||||
const enrichedVideos = [];
|
||||
for (const videoRecord of videos) {
|
||||
const video = videoRecord.get ? videoRecord.get() : { ...videoRecord };
|
||||
const visibilities = await this.getEroticVideoVisibilityEntries(video.id);
|
||||
video.visibilities = visibilities.map(entry => ({ id: entry.id, description: entry.description }));
|
||||
video.selectedUsers = await this.getEroticVideoSelectedUsernames(video.id);
|
||||
enrichedVideos.push(video);
|
||||
}
|
||||
return enrichedVideos;
|
||||
}
|
||||
|
||||
async enrichImageVisibilityMetadata(images) {
|
||||
const enrichedImages = [];
|
||||
for (const imageRecord of images) {
|
||||
const image = imageRecord.get ? imageRecord.get() : { ...imageRecord };
|
||||
const visibilities = await ImageVisibilityType.findAll({
|
||||
include: [{
|
||||
model: Image,
|
||||
where: { id: image.id },
|
||||
attributes: [],
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
});
|
||||
image.visibilities = visibilities.map(entry => ({ id: entry.id, description: entry.description }));
|
||||
image.selectedUsers = await this.getImageSelectedUsernames(image.id);
|
||||
enrichedImages.push(image);
|
||||
}
|
||||
return enrichedImages;
|
||||
}
|
||||
|
||||
async canRequesterAccessAdultFolder(folder, requesterId) {
|
||||
if (!folder || !requesterId) return false;
|
||||
if (folder.userId === requesterId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const adultAccess = await this.getAdultAccessState(requesterId);
|
||||
if (!adultAccess.adultAccessEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const folderVisibilities = await ImageVisibilityType.findAll({
|
||||
include: [{
|
||||
model: Folder,
|
||||
where: { id: folder.id },
|
||||
attributes: [],
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
});
|
||||
const descriptions = folderVisibilities.map(entry => entry.description);
|
||||
if (!descriptions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (descriptions.includes('adults') || descriptions.includes('everyone')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((descriptions.includes('friends') || descriptions.includes('friends-and-adults')) &&
|
||||
await this.areUsersFriends(folder.userId, requesterId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (descriptions.includes('selected-users')) {
|
||||
const selectedLink = await FolderVisibilityUser.findOne({
|
||||
where: {
|
||||
folderId: folder.id,
|
||||
visibilityUserId: requesterId
|
||||
}
|
||||
});
|
||||
if (selectedLink) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async canRequesterAccessAdultImage(image, requesterId) {
|
||||
if (!image || !requesterId) return false;
|
||||
if (image.userId === requesterId) {
|
||||
return true;
|
||||
}
|
||||
if (image.isModeratedHidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const folder = await Folder.findOne({
|
||||
where: {
|
||||
id: image.folderId,
|
||||
userId: image.userId,
|
||||
isAdultArea: true
|
||||
}
|
||||
});
|
||||
if (!folder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const folderAccess = await this.canRequesterAccessAdultFolder(folder, requesterId);
|
||||
if (!folderAccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const adultAccess = await this.getAdultAccessState(requesterId);
|
||||
if (!adultAccess.adultAccessEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const imageVisibilities = await ImageVisibilityType.findAll({
|
||||
include: [{
|
||||
model: Image,
|
||||
where: { id: image.id },
|
||||
attributes: [],
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
});
|
||||
const descriptions = imageVisibilities.map(entry => entry.description);
|
||||
if (!descriptions.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (descriptions.includes('adults') || descriptions.includes('everyone')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((descriptions.includes('friends') || descriptions.includes('friends-and-adults')) &&
|
||||
await this.areUsersFriends(image.userId, requesterId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (descriptions.includes('selected-users')) {
|
||||
const selectedLink = await ImageVisibilityUser.findOne({
|
||||
where: {
|
||||
imageId: image.id,
|
||||
userId: requesterId
|
||||
}
|
||||
});
|
||||
if (selectedLink) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async canRequesterAccessEroticVideo(video, requesterId) {
|
||||
if (!video || !requesterId) return false;
|
||||
if (video.userId === requesterId) {
|
||||
return true;
|
||||
}
|
||||
if (video.isModeratedHidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const adultAccess = await this.getAdultAccessState(requesterId);
|
||||
if (!adultAccess.adultAccessEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const videoVisibilities = await this.getEroticVideoVisibilityEntries(video.id);
|
||||
const descriptions = videoVisibilities.map(entry => entry.description);
|
||||
if (!descriptions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (descriptions.includes('adults') || descriptions.includes('everyone')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((descriptions.includes('friends') || descriptions.includes('friends-and-adults')) &&
|
||||
await this.areUsersFriends(video.userId, requesterId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (descriptions.includes('selected-users')) {
|
||||
const selectedLink = await EroticVideoVisibilityUser.findOne({
|
||||
where: {
|
||||
eroticVideoId: video.id,
|
||||
userId: requesterId
|
||||
}
|
||||
});
|
||||
if (selectedLink) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async resolveEroticTarget(targetType, targetId) {
|
||||
if (targetType === 'image') {
|
||||
const image = await Image.findOne({
|
||||
@@ -240,6 +608,9 @@ class SocialNetworkService extends BaseService {
|
||||
visibilityTypeId: visibilityId
|
||||
});
|
||||
}
|
||||
await this.saveFolderSelectedUsers(newFolder.id, data.selectedUsers || data.selectedUsernames || [], user.id, {
|
||||
adultOnly: isAdultArea
|
||||
});
|
||||
return newFolder;
|
||||
}
|
||||
|
||||
@@ -270,6 +641,7 @@ class SocialNetworkService extends BaseService {
|
||||
const children = await this.getSubFolders(folder.id, userId, isAdultArea);
|
||||
const visibilityTypeIds = folder.image_visibility_types.map(v => v.id);
|
||||
folder.setDataValue('visibilityTypeIds', visibilityTypeIds);
|
||||
folder.setDataValue('selectedUsers', await this.getFolderSelectedUsernames(folder.id));
|
||||
folder.setDataValue('children', children);
|
||||
folder.setDataValue('image_visibility_types', undefined);
|
||||
}
|
||||
@@ -286,7 +658,7 @@ class SocialNetworkService extends BaseService {
|
||||
}
|
||||
});
|
||||
if (!folder) throw new Error('Folder not found');
|
||||
return await Image.findAll({
|
||||
const images = await Image.findAll({
|
||||
where: {
|
||||
folderId: folder.id,
|
||||
isAdultContent: false
|
||||
@@ -295,6 +667,7 @@ class SocialNetworkService extends BaseService {
|
||||
['title', 'asc']
|
||||
]
|
||||
});
|
||||
return this.enrichImageVisibilityMetadata(images);
|
||||
}
|
||||
|
||||
async uploadImage(hashedId, file, formData) {
|
||||
@@ -302,6 +675,7 @@ class SocialNetworkService extends BaseService {
|
||||
const processedImageName = await this.processAndUploadUserImage(file, 'user');
|
||||
const newImage = await this.createImageRecord(formData, userId, file, processedImageName, { isAdultContent: false });
|
||||
await this.saveImageVisibilities(newImage.id, formData.visibility);
|
||||
await this.saveImageSelectedUsers(newImage.id, formData.selectedUsers || formData.selectedUsernames || [], userId);
|
||||
return newImage;
|
||||
}
|
||||
|
||||
@@ -406,7 +780,11 @@ class SocialNetworkService extends BaseService {
|
||||
}
|
||||
|
||||
async loadUserByName(userName) {
|
||||
return await User.findOne({ username: userName});
|
||||
return await User.findOne({
|
||||
where: {
|
||||
username: userName
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
validateFolderData(data) {
|
||||
@@ -601,27 +979,78 @@ class SocialNetworkService extends BaseService {
|
||||
const children = await this.getSubFolders(rootFolder.id, userId, true);
|
||||
const data = rootFolder.get();
|
||||
data.visibilityTypeIds = data.image_visibility_types.map(v => v.id);
|
||||
data.selectedUsers = await this.getFolderSelectedUsernames(rootFolder.id);
|
||||
delete data.image_visibility_types;
|
||||
data.children = children;
|
||||
return data;
|
||||
}
|
||||
|
||||
async getAdultFoldersByUsername(username, hashedUserId) {
|
||||
const requestingUserId = await this.requireAdultAreaAccessByHash(hashedUserId);
|
||||
const owner = await this.loadUserByName(username);
|
||||
if (!owner) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const ownerRoot = await Folder.findOne({
|
||||
where: {
|
||||
userId: owner.id,
|
||||
isAdultArea: true,
|
||||
name: 'Erotik'
|
||||
},
|
||||
include: [{
|
||||
model: ImageVisibilityType,
|
||||
through: { model: FolderImageVisibility },
|
||||
attributes: ['id']
|
||||
}]
|
||||
});
|
||||
if (!ownerRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(await this.canRequesterAccessAdultFolder(ownerRoot, requestingUserId))) {
|
||||
const error = new Error('Adult folder access denied');
|
||||
error.status = 403;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const children = await this.getAccessibleAdultFolders(ownerRoot.id, owner.id, requestingUserId);
|
||||
const rootFolder = ownerRoot.get();
|
||||
rootFolder.visibilityTypeIds = ownerRoot.image_visibility_types.map(v => v.id);
|
||||
rootFolder.selectedUsers = await this.getFolderSelectedUsernames(ownerRoot.id);
|
||||
delete rootFolder.image_visibility_types;
|
||||
rootFolder.children = children;
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
async getAdultFolderImageList(hashedId, folderId) {
|
||||
const userId = await this.requireAdultAreaAccessByHash(hashedId);
|
||||
const folder = await Folder.findOne({
|
||||
where: { id: folderId, userId, isAdultArea: true }
|
||||
where: { id: folderId, isAdultArea: true }
|
||||
});
|
||||
if (!folder) {
|
||||
throw new Error('Folder not found');
|
||||
}
|
||||
return await Image.findAll({
|
||||
if (!(await this.canRequesterAccessAdultFolder(folder, userId))) {
|
||||
const error = new Error('Access denied');
|
||||
error.status = 403;
|
||||
throw error;
|
||||
}
|
||||
const images = await Image.findAll({
|
||||
where: {
|
||||
folderId: folder.id,
|
||||
isAdultContent: true,
|
||||
userId
|
||||
userId: folder.userId
|
||||
},
|
||||
order: [['title', 'asc']]
|
||||
});
|
||||
const visibleImages = [];
|
||||
for (const image of images) {
|
||||
if (await this.canRequesterAccessAdultImage(image, userId)) {
|
||||
visibleImages.push(image);
|
||||
}
|
||||
}
|
||||
return this.enrichImageVisibilityMetadata(visibleImages);
|
||||
}
|
||||
|
||||
async createAdultFolder(hashedId, data, folderId) {
|
||||
@@ -650,6 +1079,9 @@ class SocialNetworkService extends BaseService {
|
||||
const processedImageName = await this.processAndUploadUserImage(file, 'erotic');
|
||||
const newImage = await this.createImageRecord(formData, userId, file, processedImageName, { isAdultContent: true });
|
||||
await this.saveImageVisibilities(newImage.id, formData.visibility);
|
||||
await this.saveImageSelectedUsers(newImage.id, formData.selectedUsers || formData.selectedUsernames || [], userId, {
|
||||
adultOnly: true
|
||||
});
|
||||
return newImage;
|
||||
}
|
||||
|
||||
@@ -658,13 +1090,17 @@ class SocialNetworkService extends BaseService {
|
||||
const image = await Image.findOne({
|
||||
where: {
|
||||
hash,
|
||||
userId,
|
||||
isAdultContent: true
|
||||
}
|
||||
});
|
||||
if (!image) {
|
||||
throw new Error('Image not found');
|
||||
}
|
||||
if (!(await this.canRequesterAccessAdultImage(image, userId))) {
|
||||
const error = new Error('Access denied');
|
||||
error.status = 403;
|
||||
throw error;
|
||||
}
|
||||
if (image.isModeratedHidden) {
|
||||
throw new Error('Image hidden by moderation');
|
||||
}
|
||||
@@ -677,10 +1113,33 @@ class SocialNetworkService extends BaseService {
|
||||
|
||||
async listEroticVideos(hashedId) {
|
||||
const userId = await this.requireAdultAreaAccessByHash(hashedId);
|
||||
return await EroticVideo.findAll({
|
||||
const videos = await EroticVideo.findAll({
|
||||
where: { userId },
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
return this.enrichEroticVideoVisibilityMetadata(videos);
|
||||
}
|
||||
|
||||
async getEroticVideosByUsername(username, hashedId) {
|
||||
const requestingUserId = await this.requireAdultAreaAccessByHash(hashedId);
|
||||
const owner = await this.loadUserByName(username);
|
||||
if (!owner) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const videos = await EroticVideo.findAll({
|
||||
where: { userId: owner.id },
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const visibleVideos = [];
|
||||
for (const video of videos) {
|
||||
if (await this.canRequesterAccessEroticVideo(video, requestingUserId)) {
|
||||
visibleVideos.push(video);
|
||||
}
|
||||
}
|
||||
|
||||
return this.enrichEroticVideoVisibilityMetadata(visibleVideos);
|
||||
}
|
||||
|
||||
async uploadEroticVideo(hashedId, file, formData) {
|
||||
@@ -698,7 +1157,7 @@ class SocialNetworkService extends BaseService {
|
||||
const filePath = this.buildFilePath(fileName, 'erotic-video');
|
||||
await this.saveFile(file.buffer, filePath);
|
||||
|
||||
return await EroticVideo.create({
|
||||
const video = await EroticVideo.create({
|
||||
title: formData.title || file.originalname,
|
||||
description: formData.description || null,
|
||||
originalFileName: file.originalname,
|
||||
@@ -706,16 +1165,33 @@ class SocialNetworkService extends BaseService {
|
||||
mimeType: file.mimetype,
|
||||
userId
|
||||
});
|
||||
|
||||
const visibility = formData.visibility || JSON.stringify(
|
||||
(await this.getPossibleImageVisibilities())
|
||||
.filter(entry => entry.description === 'adults')
|
||||
.map(entry => entry.id)
|
||||
);
|
||||
await this.saveEroticVideoVisibilities(video.id, visibility);
|
||||
await this.saveEroticVideoSelectedUsers(video.id, formData.selectedUsers || formData.selectedUsernames || [], userId, {
|
||||
adultOnly: true
|
||||
});
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
async getEroticVideoFilePath(hashedId, hash) {
|
||||
const userId = await this.requireAdultAreaAccessByHash(hashedId);
|
||||
const video = await EroticVideo.findOne({
|
||||
where: { hash, userId }
|
||||
where: { hash }
|
||||
});
|
||||
if (!video) {
|
||||
throw new Error('Video not found');
|
||||
}
|
||||
if (!(await this.canRequesterAccessEroticVideo(video, userId))) {
|
||||
const error = new Error('Access denied');
|
||||
error.status = 403;
|
||||
throw error;
|
||||
}
|
||||
if (video.isModeratedHidden) {
|
||||
throw new Error('Video hidden by moderation');
|
||||
}
|
||||
@@ -726,6 +1202,29 @@ class SocialNetworkService extends BaseService {
|
||||
return { filePath: videoPath, mimeType: video.mimeType };
|
||||
}
|
||||
|
||||
async changeEroticVideo(hashedUserId, videoId, payload) {
|
||||
const userId = await this.requireAdultAreaAccessByHash(hashedUserId);
|
||||
const video = await EroticVideo.findOne({
|
||||
where: {
|
||||
id: videoId,
|
||||
userId
|
||||
}
|
||||
});
|
||||
if (!video) {
|
||||
throw new Error('Video not found');
|
||||
}
|
||||
|
||||
await video.update({
|
||||
title: payload.title || video.title,
|
||||
description: payload.description ?? video.description
|
||||
});
|
||||
await this.saveEroticVideoVisibilities(videoId, payload.visibilities);
|
||||
await this.saveEroticVideoSelectedUsers(videoId, payload.selectedUsers || [], userId, {
|
||||
adultOnly: true
|
||||
});
|
||||
return video;
|
||||
}
|
||||
|
||||
async createEroticContentReport(hashedId, payload) {
|
||||
const reporterId = await this.requireAdultAreaAccessByHash(hashedId);
|
||||
const targetType = String(payload.targetType || '').trim().toLowerCase();
|
||||
@@ -807,7 +1306,7 @@ class SocialNetworkService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
async changeImage(hashedUserId, imageId, title, visibilities) {
|
||||
async changeImage(hashedUserId, imageId, title, visibilities, selectedUsers = []) {
|
||||
const userId = await this.checkUserAccess(hashedUserId);
|
||||
await this.checkUserImageAccess(userId, imageId);
|
||||
const image = await Image.findOne({ where: { id: imageId, isAdultContent: false } });
|
||||
@@ -819,10 +1318,11 @@ class SocialNetworkService extends BaseService {
|
||||
for (const visibility of visibilities) {
|
||||
await ImageImageVisibility.create({ imageId, visibilityTypeId: visibility.id });
|
||||
}
|
||||
await this.saveImageSelectedUsers(imageId, selectedUsers, userId);
|
||||
return image.folderId;
|
||||
}
|
||||
|
||||
async changeAdultImage(hashedUserId, imageId, title, visibilities) {
|
||||
async changeAdultImage(hashedUserId, imageId, title, visibilities, selectedUsers = []) {
|
||||
const userId = await this.requireAdultAreaAccessByHash(hashedUserId);
|
||||
const image = await Image.findOne({
|
||||
where: {
|
||||
@@ -839,9 +1339,38 @@ class SocialNetworkService extends BaseService {
|
||||
for (const visibility of visibilities) {
|
||||
await ImageImageVisibility.create({ imageId, visibilityTypeId: visibility.id });
|
||||
}
|
||||
await this.saveImageSelectedUsers(imageId, selectedUsers, userId, {
|
||||
adultOnly: true
|
||||
});
|
||||
return image.folderId;
|
||||
}
|
||||
|
||||
async getAccessibleAdultFolders(parentId, ownerUserId, requestingUserId) {
|
||||
const folders = await Folder.findAll({
|
||||
where: { parentId, userId: ownerUserId, isAdultArea: true },
|
||||
include: [{
|
||||
model: ImageVisibilityType,
|
||||
through: { model: FolderImageVisibility },
|
||||
attributes: ['id']
|
||||
}],
|
||||
order: [['name', 'asc']]
|
||||
});
|
||||
|
||||
const result = [];
|
||||
for (const folderRecord of folders) {
|
||||
if (!(await this.canRequesterAccessAdultFolder(folderRecord, requestingUserId))) {
|
||||
continue;
|
||||
}
|
||||
const folder = folderRecord.get();
|
||||
folder.visibilityTypeIds = folderRecord.image_visibility_types.map(v => v.id);
|
||||
folder.selectedUsers = await this.getFolderSelectedUsernames(folder.id);
|
||||
delete folder.image_visibility_types;
|
||||
folder.children = await this.getAccessibleAdultFolders(folder.id, ownerUserId, requestingUserId);
|
||||
result.push(folder);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async getFoldersByUsername(username, hashedUserId) {
|
||||
const user = await this.loadUserByName(username);
|
||||
if (!user) {
|
||||
|
||||
Reference in New Issue
Block a user