UNPKG

@ideal-photography/shared

Version:

Shared GraphQL (Apollo Server v5) and Mongoose logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, and audit logs.

410 lines (345 loc) 15.9 kB
import Media from '../../mongoDB/models/Media.js'; import { deleteFromCloudinary, createFolder, getFolderContents } from '../../utils/cloudinary.js'; import { AuthenticationError, ForbiddenError, UserInputError } from '../errors.js'; const mediaResolvers = { Query: { // Get media files with filtering and pagination getMedia: async (parent, { filter = {}, sort = { field: 'createdAt', direction: 'DESC' }, page = 1, limit = 20 }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const query = { isActive: true }; // Apply filters if (filter.category) query.category = filter.category; if (filter.type) query.type = filter.type; if (filter.status) query.status = filter.status; if (filter.isPublic !== undefined) query.isPublic = filter.isPublic; if (filter.uploadedBy) query.uploadedBy = filter.uploadedBy; if (filter.tags && filter.tags.length > 0) { query.tags = { $in: filter.tags }; } if (filter.search) { query.$or = [ { originalName: { $regex: filter.search, $options: 'i' } }, { description: { $regex: filter.search, $options: 'i' } }, { tags: { $in: [new RegExp(filter.search, 'i')] } }, { alt: { $regex: filter.search, $options: 'i' } } ]; } if (filter.dateFrom || filter.dateTo) { query.createdAt = {}; if (filter.dateFrom) query.createdAt.$gte = new Date(filter.dateFrom); if (filter.dateTo) query.createdAt.$lte = new Date(filter.dateTo); } // Apply sorting const sortOptions = {}; sortOptions[sort.field] = sort.direction === 'DESC' ? -1 : 1; const skip = (page - 1) * limit; const [media, total] = await Promise.all([ Media.find(query) .populate('uploadedBy', 'name username email') .sort(sortOptions) .skip(skip) .limit(limit), Media.countDocuments(query) ]); const totalPages = Math.ceil(total / limit); return { media, pagination: { currentPage: page, totalPages, totalItems: total, itemsPerPage: limit, hasNextPage: page < totalPages, hasPrevPage: page > 1 } }; } catch (error) { throw new Error(`Failed to fetch media: ${error.message}`); } }, // Get single media file by ID getMediaById: async (parent, { id }, { user }) => { if (!user) { throw new AuthenticationError('Authentication required'); } try { const media = await Media.findById(id) .populate('uploadedBy', 'name username email') .populate('originalMedia'); if (!media) { throw new UserInputError('Media file not found'); } // Check access permissions if (!media.isPublic && user.constructor.modelName !== 'Admin' && media.uploadedBy._id.toString() !== user.id) { throw new ForbiddenError('Access denied to this media file'); } return media; } catch (error) { throw new Error(`Failed to fetch media: ${error.message}`); } }, // Get media by category getMediaByCategory: async (parent, { category, limit = 50 }, { user }) => { if (!user) { throw new AuthenticationError('Authentication required'); } try { const query = { category, isActive: true }; // Non-admin users can only see public media if (user.constructor.modelName !== 'Admin') { query.isPublic = true; } return await Media.findByCategory(category, { limit }); } catch (error) { throw new Error(`Failed to fetch media by category: ${error.message}`); } }, // Get media uploaded by user getMediaByUploader: async (parent, { uploaderId, uploaderModel = 'User' }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { return await Media.findByUploader(uploaderId, uploaderModel); } catch (error) { throw new Error(`Failed to fetch media by uploader: ${error.message}`); } }, // Get media usage statistics getMediaStats: async (parent, args, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const stats = await Media.getUsageStats(); const totalFiles = await Media.countDocuments({ isActive: true }); const totalSizeResult = await Media.aggregate([ { $match: { isActive: true } }, { $group: { _id: null, totalSize: { $sum: '$fileSize' } } } ]); return { totalFiles, totalSize: totalSizeResult[0]?.totalSize || 0, byType: stats }; } catch (error) { throw new Error(`Failed to fetch media stats: ${error.message}`); } }, // Get folder contents from Cloudinary getFolderContents: async (parent, { folderPath, maxResults = 50, nextCursor }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const contents = await getFolderContents(folderPath, { max_results: maxResults, next_cursor: nextCursor }); return { resources: contents.resources || [], next_cursor: contents.next_cursor, total_count: contents.total_count || 0 }; } catch (error) { throw new Error(`Failed to fetch folder contents: ${error.message}`); } }, // Search media files searchMedia: async (parent, { query, filters = {}, limit = 20 }, { user }) => { if (!user) { throw new AuthenticationError('Authentication required'); } try { const searchQuery = { isActive: true, $or: [ { originalName: { $regex: query, $options: 'i' } }, { description: { $regex: query, $options: 'i' } }, { tags: { $in: [new RegExp(query, 'i')] } }, { alt: { $regex: query, $options: 'i' } } ] }; // Apply additional filters if (filters.category) searchQuery.category = filters.category; if (filters.type) searchQuery.type = filters.type; if (user.constructor.modelName !== 'Admin') { searchQuery.isPublic = true; } return await Media.find(searchQuery) .populate('uploadedBy', 'name username email') .limit(limit) .sort('-createdAt'); } catch (error) { throw new Error(`Failed to search media: ${error.message}`); } } }, Mutation: { // Update media metadata updateMedia: async (parent, { id, input }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const media = await Media.findById(id); if (!media) { throw new UserInputError('Media file not found'); } // Update fields Object.keys(input).forEach(key => { if (input[key] !== undefined) { media[key] = input[key]; } }); await media.save(); return await Media.findById(id).populate('uploadedBy', 'name username email'); } catch (error) { throw new Error(`Failed to update media: ${error.message}`); } }, // Delete media file deleteMedia: async (parent, { id }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const media = await Media.findById(id); if (!media) { throw new UserInputError('Media file not found'); } // Delete from Cloudinary await deleteFromCloudinary(media.cloudinaryId, media.type === 'video' ? 'video' : 'image'); // Delete from database await Media.findByIdAndDelete(id); return true; } catch (error) { throw new Error(`Failed to delete media: ${error.message}`); } }, // Delete multiple media files deleteMultipleMedia: async (parent, { ids }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const mediaFiles = await Media.find({ _id: { $in: ids } }); // Delete from Cloudinary for (const media of mediaFiles) { try { await deleteFromCloudinary(media.cloudinaryId, media.type === 'video' ? 'video' : 'image'); } catch (error) { console.error(`Failed to delete ${media.cloudinaryId} from Cloudinary:`, error); } } // Delete from database await Media.deleteMany({ _id: { $in: ids } }); return true; } catch (error) { throw new Error(`Failed to delete multiple media files: ${error.message}`); } }, // Create folder in Cloudinary createMediaFolder: async (parent, { folderPath }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { await createFolder(folderPath); return true; } catch (error) { throw new Error(`Failed to create folder: ${error.message}`); } }, // Record media usage recordMediaUsage: async (parent, { mediaId, modelName, modelId, field }, { user }) => { if (!user) { throw new AuthenticationError('Authentication required'); } try { const media = await Media.findById(mediaId); if (!media) { throw new UserInputError('Media file not found'); } await media.recordUsage(modelName, modelId, field); return await Media.findById(mediaId).populate('uploadedBy', 'name username email'); } catch (error) { throw new Error(`Failed to record media usage: ${error.message}`); } }, // Update media tags updateMediaTags: async (parent, { id, tags, action = 'REPLACE' }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const media = await Media.findById(id); if (!media) { throw new UserInputError('Media file not found'); } switch (action) { case 'REPLACE': media.tags = tags; break; case 'ADD': media.tags = [...new Set([...media.tags, ...tags])]; break; case 'REMOVE': media.tags = media.tags.filter(tag => !tags.includes(tag)); break; } await media.save(); return await Media.findById(id).populate('uploadedBy', 'name username email'); } catch (error) { throw new Error(`Failed to update media tags: ${error.message}`); } }, // Archive/Unarchive media toggleMediaArchive: async (parent, { id }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const media = await Media.findById(id); if (!media) { throw new UserInputError('Media file not found'); } media.status = media.status === 'archived' ? 'ready' : 'archived'; await media.save(); return await Media.findById(id).populate('uploadedBy', 'name username email'); } catch (error) { throw new Error(`Failed to toggle media archive status: ${error.message}`); } }, // Bulk update media bulkUpdateMedia: async (parent, { ids, updates }, { user }) => { if (!user || user.constructor.modelName !== 'Admin') { throw new ForbiddenError('Admin access required'); } try { const updateFields = {}; Object.keys(updates).forEach(key => { if (updates[key] !== undefined) { updateFields[key] = updates[key]; } }); await Media.updateMany( { _id: { $in: ids } }, { $set: updateFields } ); return await Media.find({ _id: { $in: ids } }) .populate('uploadedBy', 'name username email'); } catch (error) { throw new Error(`Failed to bulk update media: ${error.message}`); } } } }; export default mediaResolvers;