/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import type { FastifyRequest } from 'fastify'; import { prisma } from '../lib/prisma'; import { AuditService } from './audit.service'; import { AchievementService } from './achievement.service'; import type { Like, LikeCountDto, LikedItemDto, LikeResponse } from '@library/shared-types'; import { AuditAction, AuditCategory, AchievementCategory } from '@library/shared-types'; export class LikeService { async toggleLike(userId: string, entityType: Like['entityType'], entityId: string, req: FastifyRequest): Promise { // Check if like exists const existingLike = await prisma.like.findUnique({ where: { userId_entityType_entityId: { userId, entityType, entityId } } }); if (existingLike) { // Unlike await prisma.like.delete({ where: { id: existingLike.id } }); await AuditService.logFromRequest(req, { action: AuditAction.unlike, category: AuditCategory.content, resourceType: entityType, resourceId: entityId, details: `Unliked ${entityType}` }); const count = await this.getLikeCount(entityType, entityId); return { liked: false, count }; } else { // Like await prisma.like.create({ data: { userId, entityType, entityId } }); await AuditService.logFromRequest(req, { action: AuditAction.like, category: AuditCategory.content, resourceType: entityType, resourceId: entityId, details: `Liked ${entityType}` }); // Check for like achievements const achievementService = new AchievementService(); await achievementService.checkAchievements( userId, AchievementCategory.Like, req ); const count = await this.getLikeCount(entityType, entityId); return { liked: true, count }; } } async getLikeCount(entityType: Like['entityType'], entityId: string): Promise { return await prisma.like.count({ where: { entityType, entityId } }); } async getUserLikeStatus(userId: string, entityType: Like['entityType'], entityId: string): Promise { const like = await prisma.like.findUnique({ where: { userId_entityType_entityId: { userId, entityType, entityId } } }); return !!like; } async getLikeCounts(entityType: Like['entityType'], entityIds: string[]): Promise { const likes = await prisma.like.groupBy({ by: ['entityId'], where: { entityType, entityId: { in: entityIds } }, _count: true }); return likes.map(like => ({ entityId: like.entityId, entityType, count: like._count })); } async getUserLikeStatuses(userId: string, entityType: Like['entityType'], entityIds: string[]): Promise> { const likes = await prisma.like.findMany({ where: { userId, entityType, entityId: { in: entityIds } }, select: { entityId: true } }); const likeMap: Record = {}; entityIds.forEach(id => { likeMap[id] = false; }); likes.forEach(like => { likeMap[like.entityId] = true; }); return likeMap; } async getUserLikedItems(userId: string, entityType?: Like['entityType']): Promise { const likes = await prisma.like.findMany({ where: { userId, ...(entityType ? { entityType } : {}) }, orderBy: { createdAt: 'desc' } }); // Fetch the actual items for each like const likedItems: LikedItemDto[] = []; for (const like of likes) { let item: any = null; switch (like.entityType) { case 'book': item = await prisma.book.findUnique({ where: { id: like.entityId } }); break; case 'game': item = await prisma.game.findUnique({ where: { id: like.entityId } }); break; case 'show': item = await prisma.show.findUnique({ where: { id: like.entityId } }); break; case 'manga': item = await prisma.manga.findUnique({ where: { id: like.entityId } }); break; case 'music': item = await prisma.music.findUnique({ where: { id: like.entityId } }); break; case 'art': item = await prisma.art.findUnique({ where: { id: like.entityId } }); break; } if (item) { likedItems.push({ like: { ...like, entityType: like.entityType as Like['entityType'] }, item }); } } return likedItems; } }