generated from nhcarrigan/template
219 lines
6.4 KiB
TypeScript
219 lines
6.4 KiB
TypeScript
/**
|
|
* @copyright 2026 NHCarrigan
|
|
* @license Naomi's Public License
|
|
* @author Naomi Carrigan
|
|
*/
|
|
|
|
import { FastifyPluginAsync } from "fastify";
|
|
import { Music, CreateMusicDto, UpdateMusicDto, Comment, CreateCommentDto, AuditAction, AuditCategory } from "@library/shared-types";
|
|
import { MusicService } from "../../services/music.service";
|
|
import { CommentService } from "../../services/comment.service";
|
|
import { AuditService } from "../../services/audit.service";
|
|
import { adminGuard } from "../../middleware/admin-guard";
|
|
import { bannedGuard } from "../../middleware/banned-guard";
|
|
|
|
const musicRoutes: FastifyPluginAsync = async (app) => {
|
|
const musicService = new MusicService();
|
|
const commentService = new CommentService();
|
|
|
|
/**
|
|
* Get all music (public route).
|
|
*/
|
|
app.get<{ Reply: Music[] }>("/", async () => {
|
|
return musicService.getAllMusic();
|
|
});
|
|
|
|
/**
|
|
* Get single music item by ID (public route).
|
|
*/
|
|
app.get<{ Params: { id: string }; Reply: Music | null }>(
|
|
"/:id",
|
|
async (request) => {
|
|
const { id } = request.params;
|
|
return musicService.getMusicById(id);
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Create new music item (admin only).
|
|
*/
|
|
app.post<{ Body: CreateMusicDto; Reply: Music }>(
|
|
"/",
|
|
{
|
|
preValidation: [app.authenticate, adminGuard],
|
|
preHandler: [app.csrfProtection],
|
|
},
|
|
async (request) => {
|
|
const music = await musicService.createMusic(request.body);
|
|
await AuditService.logFromRequest(request, {
|
|
action: AuditAction.ENTRY_CREATE,
|
|
category: AuditCategory.CONTENT,
|
|
resourceType: "music",
|
|
resourceId: music.id,
|
|
details: `Created music: ${music.title}`,
|
|
});
|
|
return music;
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Update music item by ID (admin only).
|
|
*/
|
|
app.put<{
|
|
Params: { id: string };
|
|
Body: UpdateMusicDto;
|
|
Reply: Music | null;
|
|
}>(
|
|
"/:id",
|
|
{
|
|
preValidation: [app.authenticate, adminGuard],
|
|
preHandler: [app.csrfProtection],
|
|
},
|
|
async (request) => {
|
|
const { id } = request.params;
|
|
const music = await musicService.updateMusic(id, request.body);
|
|
if (music) {
|
|
await AuditService.logFromRequest(request, {
|
|
action: AuditAction.ENTRY_UPDATE,
|
|
category: AuditCategory.CONTENT,
|
|
resourceType: "music",
|
|
resourceId: id,
|
|
details: `Updated music: ${music.title}`,
|
|
});
|
|
}
|
|
return music;
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Delete music item by ID (admin only).
|
|
*/
|
|
app.delete<{ Params: { id: string }; Reply: { success: boolean } }>(
|
|
"/:id",
|
|
{
|
|
preValidation: [app.authenticate, adminGuard],
|
|
preHandler: [app.csrfProtection],
|
|
},
|
|
async (request) => {
|
|
const { id } = request.params;
|
|
await musicService.deleteMusic(id);
|
|
await AuditService.logFromRequest(request, {
|
|
action: AuditAction.ENTRY_DELETE,
|
|
category: AuditCategory.CONTENT,
|
|
resourceType: "music",
|
|
resourceId: id,
|
|
details: `Deleted music with ID: ${id}`,
|
|
});
|
|
return { success: true };
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Get comments for a music item (public route).
|
|
*/
|
|
app.get<{ Params: { id: string }; Reply: Comment[] }>(
|
|
"/:id/comments",
|
|
async (request) => {
|
|
const { id } = request.params;
|
|
return commentService.getCommentsForMusic(id);
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Add comment to a music item (authenticated users).
|
|
*/
|
|
app.post<{ Params: { id: string }; Body: CreateCommentDto; Reply: Comment }>(
|
|
"/:id/comments",
|
|
{
|
|
preValidation: [app.authenticate, bannedGuard],
|
|
preHandler: [app.csrfProtection],
|
|
},
|
|
async (request) => {
|
|
const { id } = request.params;
|
|
const userId = request.user.id;
|
|
const comment = await commentService.createCommentForMusic(id, userId, request.body);
|
|
await AuditService.logFromRequest(request, {
|
|
action: AuditAction.COMMENT_CREATE,
|
|
category: AuditCategory.CONTENT,
|
|
resourceType: "music",
|
|
resourceId: id,
|
|
details: `Added comment to music`,
|
|
});
|
|
return comment;
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Update comment (owner or admin).
|
|
*/
|
|
app.put<{ Params: { id: string; commentId: string }; Body: CreateCommentDto; Reply: Comment | { error: string } }>(
|
|
"/:id/comments/:commentId",
|
|
{
|
|
preValidation: [app.authenticate],
|
|
preHandler: [app.csrfProtection],
|
|
},
|
|
async (request, reply) => {
|
|
const { id, commentId } = request.params;
|
|
const userId = request.user.id;
|
|
const isAdmin = request.user.isAdmin;
|
|
|
|
const verification = await commentService.verifyCommentOwnership(commentId, "music", id);
|
|
|
|
if (!verification.exists) {
|
|
return reply.code(404).send({ error: "Comment not found" });
|
|
}
|
|
|
|
if (verification.comment?.userId !== userId && !isAdmin) {
|
|
return reply.code(403).send({ error: "You can only edit your own comments" });
|
|
}
|
|
|
|
const comment = await commentService.updateComment(commentId, request.body.content);
|
|
await AuditService.logFromRequest(request, {
|
|
action: AuditAction.COMMENT_UPDATE,
|
|
category: AuditCategory.CONTENT,
|
|
resourceType: "music",
|
|
resourceId: id,
|
|
details: `Updated comment ${commentId} on music`,
|
|
});
|
|
return comment;
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Delete comment (owner or admin).
|
|
*/
|
|
app.delete<{ Params: { id: string; commentId: string }; Reply: { success: boolean } | { error: string } }>(
|
|
"/:id/comments/:commentId",
|
|
{
|
|
preValidation: [app.authenticate],
|
|
preHandler: [app.csrfProtection],
|
|
},
|
|
async (request, reply) => {
|
|
const { id, commentId } = request.params;
|
|
const userId = request.user.id;
|
|
const isAdmin = request.user.isAdmin;
|
|
|
|
const verification = await commentService.verifyCommentOwnership(commentId, "music", id);
|
|
|
|
if (!verification.exists) {
|
|
return reply.code(404).send({ error: "Comment not found" });
|
|
}
|
|
|
|
if (verification.comment?.userId !== userId && !isAdmin) {
|
|
return reply.code(403).send({ error: "You can only delete your own comments" });
|
|
}
|
|
|
|
await commentService.deleteComment(commentId);
|
|
await AuditService.logFromRequest(request, {
|
|
action: AuditAction.COMMENT_DELETE,
|
|
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
|
resourceType: "music",
|
|
resourceId: id,
|
|
details: `Deleted comment ${commentId} from music`,
|
|
});
|
|
return { success: true };
|
|
}
|
|
);
|
|
};
|
|
|
|
export default musicRoutes; |