generated from nhcarrigan/template
feat: add ability to like books
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { FastifyPluginAsync } from "fastify";
|
||||
import { CreateLikeDto, LikeResponse, LikedItemDto, AuditAction, AuditCategory } from "@library/shared-types";
|
||||
import { LikeService } from "../../services/like.service";
|
||||
import { AuditService } from "../../services/audit.service";
|
||||
import { bannedGuard } from "../../middleware/banned-guard";
|
||||
|
||||
const likesRoutes: FastifyPluginAsync = async (app) => {
|
||||
const likeService = new LikeService();
|
||||
|
||||
/**
|
||||
* Toggle like on an item (authenticated users).
|
||||
*/
|
||||
app.post<{ Body: CreateLikeDto; Reply: LikeResponse }>(
|
||||
"/toggle",
|
||||
{
|
||||
preValidation: [app.authenticate, bannedGuard],
|
||||
preHandler: [app.csrfProtection],
|
||||
},
|
||||
async (request) => {
|
||||
const userId = request.user.id;
|
||||
const { entityType, entityId } = request.body;
|
||||
const result = await likeService.toggleLike(userId, entityType, entityId, request);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get like count for an item (public route).
|
||||
*/
|
||||
app.get<{
|
||||
Querystring: { entityType: string; entityId: string };
|
||||
}>(
|
||||
"/count",
|
||||
async (request, reply) => {
|
||||
const { entityType, entityId } = request.query;
|
||||
|
||||
// Validate entityType
|
||||
const validTypes = ['book', 'game', 'show', 'manga', 'music', 'art'];
|
||||
if (!validTypes.includes(entityType)) {
|
||||
reply.code(400);
|
||||
return { error: "Invalid entity type" };
|
||||
}
|
||||
|
||||
if (!entityId) {
|
||||
reply.code(400);
|
||||
return { error: "Entity ID is required" };
|
||||
}
|
||||
|
||||
const count = await likeService.getLikeCount(entityType as any, entityId);
|
||||
return { count };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if current user has liked an item (authenticated users).
|
||||
*/
|
||||
app.get<{
|
||||
Querystring: { entityType: string; entityId: string };
|
||||
}>(
|
||||
"/status",
|
||||
{
|
||||
preValidation: [app.authenticate],
|
||||
},
|
||||
async (request, reply) => {
|
||||
const userId = request.user.id;
|
||||
const { entityType, entityId } = request.query;
|
||||
|
||||
// Validate entityType
|
||||
const validTypes = ['book', 'game', 'show', 'manga', 'music', 'art'];
|
||||
if (!validTypes.includes(entityType)) {
|
||||
reply.code(400);
|
||||
return { error: "Invalid entity type" };
|
||||
}
|
||||
|
||||
if (!entityId) {
|
||||
reply.code(400);
|
||||
return { error: "Entity ID is required" };
|
||||
}
|
||||
|
||||
const liked = await likeService.getUserLikeStatus(userId, entityType as any, entityId);
|
||||
return { liked };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all items liked by the current user (authenticated users).
|
||||
*/
|
||||
app.get<{
|
||||
Querystring: { entityType?: string };
|
||||
}>(
|
||||
"/user",
|
||||
{
|
||||
preValidation: [app.authenticate],
|
||||
},
|
||||
async (request, reply) => {
|
||||
const userId = request.user.id;
|
||||
const { entityType } = request.query;
|
||||
|
||||
// Validate entityType if provided
|
||||
if (entityType) {
|
||||
const validTypes = ['book', 'game', 'show', 'manga', 'music', 'art'];
|
||||
if (!validTypes.includes(entityType)) {
|
||||
reply.code(400);
|
||||
return { error: "Invalid entity type" };
|
||||
}
|
||||
}
|
||||
|
||||
const likedItems = await likeService.getUserLikedItems(userId, entityType as any);
|
||||
return likedItems;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get bulk like statuses for multiple items (authenticated users).
|
||||
* Useful for efficiently loading like status for lists.
|
||||
*/
|
||||
app.post<{
|
||||
Body: { items: Array<{ entityType: string; entityId: string }> };
|
||||
}>(
|
||||
"/bulk-status",
|
||||
{
|
||||
preValidation: [app.authenticate],
|
||||
},
|
||||
async (request, reply) => {
|
||||
const userId = request.user.id;
|
||||
const { items } = request.body;
|
||||
|
||||
if (!items || !Array.isArray(items)) {
|
||||
reply.code(400);
|
||||
return { error: "Items array is required" };
|
||||
}
|
||||
|
||||
const validTypes = ['book', 'game', 'show', 'manga', 'music', 'art'];
|
||||
const results = await Promise.all(
|
||||
items.map(async (item) => {
|
||||
if (!validTypes.includes(item.entityType)) {
|
||||
return {
|
||||
entityType: item.entityType,
|
||||
entityId: item.entityId,
|
||||
liked: false,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const [liked, count] = await Promise.all([
|
||||
likeService.getUserLikeStatus(userId, item.entityType as any, item.entityId),
|
||||
likeService.getLikeCount(item.entityType as any, item.entityId),
|
||||
]);
|
||||
|
||||
return {
|
||||
entityType: item.entityType,
|
||||
entityId: item.entityId,
|
||||
liked,
|
||||
count,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default likesRoutes;
|
||||
Reference in New Issue
Block a user