generated from nhcarrigan/template
feat: add ability to like books
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @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 type { Like, LikeCountDto, LikedItemDto, LikeResponse } from '@library/shared-types';
|
||||
import { AuditAction, AuditCategory } from '@library/shared-types';
|
||||
|
||||
export class LikeService {
|
||||
async toggleLike(userId: string, entityType: Like['entityType'], entityId: string, req: FastifyRequest): Promise<LikeResponse> {
|
||||
// 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}`
|
||||
});
|
||||
|
||||
const count = await this.getLikeCount(entityType, entityId);
|
||||
return { liked: true, count };
|
||||
}
|
||||
}
|
||||
|
||||
async getLikeCount(entityType: Like['entityType'], entityId: string): Promise<number> {
|
||||
return await prisma.like.count({
|
||||
where: {
|
||||
entityType,
|
||||
entityId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getUserLikeStatus(userId: string, entityType: Like['entityType'], entityId: string): Promise<boolean> {
|
||||
const like = await prisma.like.findUnique({
|
||||
where: {
|
||||
userId_entityType_entityId: {
|
||||
userId,
|
||||
entityType,
|
||||
entityId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return !!like;
|
||||
}
|
||||
|
||||
async getLikeCounts(entityType: Like['entityType'], entityIds: string[]): Promise<LikeCountDto[]> {
|
||||
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<Record<string, boolean>> {
|
||||
const likes = await prisma.like.findMany({
|
||||
where: {
|
||||
userId,
|
||||
entityType,
|
||||
entityId: { in: entityIds }
|
||||
},
|
||||
select: {
|
||||
entityId: true
|
||||
}
|
||||
});
|
||||
|
||||
const likeMap: Record<string, boolean> = {};
|
||||
entityIds.forEach(id => {
|
||||
likeMap[id] = false;
|
||||
});
|
||||
likes.forEach(like => {
|
||||
likeMap[like.entityId] = true;
|
||||
});
|
||||
|
||||
return likeMap;
|
||||
}
|
||||
|
||||
async getUserLikedItems(userId: string, entityType?: Like['entityType']): Promise<LikedItemDto[]> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user