import prisma from '../../lib/prisma'; import { MaterialDTO, MaterialType } from '../../types'; export const MaterialService = { /** * Fetch all materials with relations (Author, Comments) */ async getAllMaterials(filterType?: MaterialType, page = 1, limit = 20, search?: string) { const where: any = filterType ? { type: filterType } : {}; if (search && search.trim()) { const q = search.trim(); where.OR = [ { title: { contains: q, mode: 'insensitive' } }, { description: { contains: q, mode: 'insensitive' } }, { tags: { some: { name: { contains: q, mode: 'insensitive' } } } } ]; } const [materials, total] = await Promise.all([ prisma.material.findMany({ where, include: { author: { select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true } }, comments: { include: { author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } } }, orderBy: { createdAt: 'desc' } }, favorites: true, // To count them tags: true }, orderBy: { createdAt: 'desc' }, skip: (page - 1) * limit, take: limit }), prisma.material.count({ where }) ]); // Transform Prisma Result to DTO const items = materials.map(m => ({ id: m.id, title: m.title, description: m.description, type: m.type as MaterialType, contentUrl: m.contentUrl || undefined, codeSnippet: m.codeSnippet || undefined, language: m.language || undefined, author: { ...m.author, role: m.author.role as any, createdAt: m.author.createdAt.toISOString(), lastLogin: m.author.lastLogin?.toISOString() || '', avatarUrl: m.author.avatarUrl || '' }, stats: { views: m.views, downloads: m.downloads, favorites: m.favorites.length }, tags: m.tags.map(t => t.name), createdAt: m.createdAt.toISOString(), comments: m.comments.map(c => ({ id: c.id, content: c.content, createdAt: c.createdAt.toISOString(), author: { ...c.author, role: c.author.role as any, createdAt: c.author.createdAt.toISOString(), lastLogin: c.author.lastLogin?.toISOString() || '', avatarUrl: c.author.avatarUrl || '' } })) })); return { items, total }; }, async getMaterialsByAuthor(userId: string, page = 1, limit = 20) { const [materials, total] = await Promise.all([ prisma.material.findMany({ where: { authorId: userId }, include: { author: { select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true } }, comments: { include: { author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } } }, orderBy: { createdAt: 'desc' } }, favorites: true, tags: true }, orderBy: { createdAt: 'desc' }, skip: (page - 1) * limit, take: limit }), prisma.material.count({ where: { authorId: userId } }) ]); const items = materials.map(m => ({ id: m.id, title: m.title, description: m.description, type: m.type as MaterialType, contentUrl: m.contentUrl || undefined, codeSnippet: m.codeSnippet || undefined, language: m.language || undefined, author: { ...m.author, role: m.author.role as any, createdAt: m.author.createdAt.toISOString(), lastLogin: m.author.lastLogin?.toISOString() || '', avatarUrl: m.author.avatarUrl || '' }, stats: { views: m.views, downloads: m.downloads, favorites: m.favorites.length }, tags: m.tags.map(t => t.name), createdAt: m.createdAt.toISOString(), comments: m.comments.map(c => ({ id: c.id, content: c.content, createdAt: c.createdAt.toISOString(), author: { ...c.author, role: c.author.role as any, createdAt: c.author.createdAt.toISOString(), lastLogin: c.author.lastLogin?.toISOString() || '', avatarUrl: c.author.avatarUrl || '' } })) })); return { items, total }; }, async getFavoritedMaterialsByUser(userId: string, page = 1, limit = 20) { const where = { favorites: { some: { userId } } }; const [materials, total] = await Promise.all([ prisma.material.findMany({ where: { favorites: { some: { userId } } }, include: { author: { select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true } }, comments: { include: { author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } } }, orderBy: { createdAt: 'desc' } }, favorites: true, tags: true }, orderBy: { createdAt: 'desc' }, skip: (page - 1) * limit, take: limit }), prisma.material.count({ where }) ]); const items = materials.map(m => ({ id: m.id, title: m.title, description: m.description, type: m.type as MaterialType, contentUrl: m.contentUrl || undefined, codeSnippet: m.codeSnippet || undefined, language: m.language || undefined, author: { ...m.author, role: m.author.role as any, createdAt: m.author.createdAt.toISOString(), lastLogin: m.author.lastLogin?.toISOString() || '', avatarUrl: m.author.avatarUrl || '' }, stats: { views: m.views, downloads: m.downloads, favorites: m.favorites.length }, tags: m.tags.map(t => t.name), createdAt: m.createdAt.toISOString(), comments: m.comments.map(c => ({ id: c.id, content: c.content, createdAt: c.createdAt.toISOString(), author: { ...c.author, role: c.author.role as any, createdAt: c.author.createdAt.toISOString(), lastLogin: c.author.lastLogin?.toISOString() || '', avatarUrl: c.author.avatarUrl || '' } })) })); return { items, total }; }, /** * Create a new Material */ async createMaterial(userId: string, data: any) { return prisma.material.create({ data: { title: data.title, description: data.description, type: data.type, codeSnippet: data.codeSnippet, contentUrl: data.contentUrl, language: data.language || 'text', authorId: userId, tags: { connectOrCreate: data.tags.map((tag: string) => ({ where: { name: tag }, create: { name: tag } })) } } }); }, async deleteMaterial(id: string) { return prisma.material.delete({ where: { id } }); }, async incrementView(id: string) { return prisma.material.update({ where: { id }, data: { views: { increment: 1 } } }); }, /** * Get material by ID with all relations */ async getMaterialById(id: string): Promise { const material = await prisma.material.findUnique({ where: { id }, include: { author: { select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true } }, comments: { include: { author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } } }, orderBy: { createdAt: 'desc' } }, favorites: true, tags: true } }); if (!material) return null; // Also increment view count await this.incrementView(id); // Transform to DTO return { id: material.id, title: material.title, description: material.description, type: material.type as MaterialType, contentUrl: material.contentUrl || undefined, codeSnippet: material.codeSnippet || undefined, language: material.language || undefined, author: { ...material.author, role: material.author.role as any, createdAt: material.author.createdAt.toISOString(), lastLogin: material.author.lastLogin?.toISOString() || '', avatarUrl: material.author.avatarUrl || '' }, stats: { views: material.views + 1, // Include the increment we just did downloads: material.downloads, favorites: material.favorites.length }, tags: material.tags.map(t => t.name), createdAt: material.createdAt.toISOString(), comments: material.comments.map(c => ({ id: c.id, content: c.content, createdAt: c.createdAt.toISOString(), author: { ...c.author, role: c.author.role as any, createdAt: c.author.createdAt.toISOString(), lastLogin: c.author.lastLogin?.toISOString() || '', avatarUrl: c.author.avatarUrl || '' } })) }; }, /** * Add a comment to a material */ async addComment(materialId: string, userId: string, content: string) { return prisma.comment.create({ data: { content, authorId: userId, materialId }, include: { author: { select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true } } } }); }, /** * Toggle favorite status for a material * Returns the new favorite count */ async toggleFavorite(materialId: string, userId: string): Promise { // Check if already favorited const existing = await prisma.favorite.findUnique({ where: { userId_materialId: { userId, materialId } } }); if (existing) { // Remove favorite await prisma.favorite.delete({ where: { userId_materialId: { userId, materialId } } }); } else { // Add favorite await prisma.favorite.create({ data: { userId, materialId } }); } // Return new favorite count const count = await prisma.favorite.count({ where: { materialId } }); return count; }, /** * Increment download count */ async incrementDownload(id: string) { return prisma.material.update({ where: { id }, data: { downloads: { increment: 1 } } }); } };