generated from nhcarrigan/template
feat: multiple improvements to library functionality #50
@@ -7,4 +7,5 @@ module.exports = {
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../coverage/api',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
};
|
||||
|
||||
@@ -15,6 +15,6 @@ describe('GET /', () => {
|
||||
url: '/',
|
||||
});
|
||||
|
||||
expect(response.json()).toEqual({ message: 'Hello API' });
|
||||
expect(response.json()).toEqual({ version: expect.any(String) });
|
||||
});
|
||||
});
|
||||
|
||||
+4
-4
@@ -13,8 +13,8 @@ export async function app(fastify: FastifyInstance, opts: AppOptions) {
|
||||
// Log CSRF validation failures
|
||||
if (error.code === 'FST_CSRF_INVALID_TOKEN' || error.code === 'FST_CSRF_MISSING_SECRET') {
|
||||
await AuditService.log({
|
||||
action: AuditAction.CSRF_VALIDATION_FAILED,
|
||||
category: AuditCategory.SECURITY,
|
||||
action: AuditAction.csrfValidationFailed,
|
||||
category: AuditCategory.security,
|
||||
details: `CSRF validation failed: ${error.message}, URL: ${request.url}`,
|
||||
success: false,
|
||||
}, request).catch(() => {
|
||||
@@ -25,8 +25,8 @@ export async function app(fastify: FastifyInstance, opts: AppOptions) {
|
||||
// Log unauthorized access attempts
|
||||
if (error.statusCode === 401 || error.statusCode === 403) {
|
||||
await AuditService.log({
|
||||
action: AuditAction.UNAUTHORIZED_ACCESS,
|
||||
category: AuditCategory.SECURITY,
|
||||
action: AuditAction.unauthorizedAccess,
|
||||
category: AuditCategory.security,
|
||||
details: `Unauthorized access attempt: ${error.message}, URL: ${request.url}`,
|
||||
success: false,
|
||||
}, request).catch(() => {
|
||||
|
||||
@@ -82,7 +82,9 @@ const authPlugin: FastifyPluginAsync = async (app) => {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch (err) {
|
||||
throw app.httpErrors.unauthorized("Invalid token");
|
||||
const error = new Error("Invalid token");
|
||||
(error as any).statusCode = 401;
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,8 +17,8 @@ const rateLimitPlugin: FastifyPluginAsync = async (app) => {
|
||||
errorResponseBuilder: (request) => {
|
||||
// Log rate limit exceeded event
|
||||
AuditService.log({
|
||||
action: AuditAction.RATE_LIMIT_EXCEEDED,
|
||||
category: AuditCategory.SECURITY,
|
||||
action: AuditAction.rateLimitExceeded,
|
||||
category: AuditCategory.security,
|
||||
details: `Rate limit exceeded for URL: ${request.url}`,
|
||||
success: false,
|
||||
}, request).catch(() => {
|
||||
|
||||
@@ -46,8 +46,8 @@ const artRoutes: FastifyPluginAsync = async (app) => {
|
||||
async (request) => {
|
||||
const art = await artService.createArt(request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "art",
|
||||
resourceId: art.id,
|
||||
details: `Created art: ${art.title}`,
|
||||
@@ -74,8 +74,8 @@ const artRoutes: FastifyPluginAsync = async (app) => {
|
||||
const art = await artService.updateArt(id, request.body);
|
||||
if (art) {
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "art",
|
||||
resourceId: id,
|
||||
details: `Updated art: ${art.title}`,
|
||||
@@ -98,8 +98,8 @@ const artRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { id } = request.params;
|
||||
await artService.deleteArt(id);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "art",
|
||||
resourceId: id,
|
||||
details: `Deleted art with ID: ${id}`,
|
||||
@@ -133,8 +133,8 @@ const artRoutes: FastifyPluginAsync = async (app) => {
|
||||
const userId = request.user.id;
|
||||
const comment = await commentService.createCommentForArt(id, userId, request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "art",
|
||||
resourceId: id,
|
||||
details: `Added comment to art`,
|
||||
@@ -169,8 +169,8 @@ const artRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
const comment = await commentService.updateComment(commentId, request.body.content);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "art",
|
||||
resourceId: id,
|
||||
details: `Updated comment ${commentId} on art`,
|
||||
@@ -205,8 +205,8 @@ const artRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
await commentService.deleteComment(commentId);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_DELETE,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.commentDelete,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "art",
|
||||
resourceId: id,
|
||||
details: `Deleted comment ${commentId} from art`,
|
||||
|
||||
@@ -85,8 +85,8 @@ const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
// Log successful login
|
||||
await AuditService.log({
|
||||
action: AuditAction.LOGIN,
|
||||
category: AuditCategory.AUTH,
|
||||
action: AuditAction.login,
|
||||
category: AuditCategory.auth,
|
||||
userId: user.id,
|
||||
details: `User ${user.username} logged in via Discord`,
|
||||
success: true,
|
||||
@@ -114,8 +114,8 @@ const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
} catch (error) {
|
||||
// Log failed login attempt
|
||||
await AuditService.log({
|
||||
action: AuditAction.LOGIN_FAILED,
|
||||
category: AuditCategory.SECURITY,
|
||||
action: AuditAction.loginFailed,
|
||||
category: AuditCategory.security,
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
success: false,
|
||||
}, request);
|
||||
@@ -229,8 +229,8 @@ const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
const user = request.user as { id?: string; username?: string };
|
||||
if (user?.id) {
|
||||
await AuditService.log({
|
||||
action: AuditAction.LOGOUT,
|
||||
category: AuditCategory.AUTH,
|
||||
action: AuditAction.logout,
|
||||
category: AuditCategory.auth,
|
||||
userId: user.id,
|
||||
details: `User ${user.username ?? "unknown"} logged out`,
|
||||
success: true,
|
||||
|
||||
@@ -46,8 +46,8 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
async (request) => {
|
||||
const book = await bookService.createBook(request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "book",
|
||||
resourceId: book.id,
|
||||
details: `Created book: ${book.title}`,
|
||||
@@ -74,8 +74,8 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
const book = await bookService.updateBook(id, request.body);
|
||||
if (book) {
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "book",
|
||||
resourceId: id,
|
||||
details: `Updated book: ${book.title}`,
|
||||
@@ -98,8 +98,8 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { id } = request.params;
|
||||
await bookService.deleteBook(id);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "book",
|
||||
resourceId: id,
|
||||
details: `Deleted book with ID: ${id}`,
|
||||
@@ -133,8 +133,8 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
const userId = request.user.id;
|
||||
const comment = await commentService.createCommentForBook(id, userId, request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "book",
|
||||
resourceId: id,
|
||||
details: `Added comment to book`,
|
||||
@@ -169,8 +169,8 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
const comment = await commentService.updateComment(commentId, request.body.content);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "book",
|
||||
resourceId: id,
|
||||
details: `Updated comment ${commentId} on book`,
|
||||
@@ -205,8 +205,8 @@ const booksRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
await commentService.deleteComment(commentId);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_DELETE,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.commentDelete,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "book",
|
||||
resourceId: id,
|
||||
details: `Deleted comment ${commentId} from book`,
|
||||
|
||||
@@ -40,8 +40,8 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
async (request) => {
|
||||
const game = await gameService.createGame(request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "game",
|
||||
resourceId: game.id,
|
||||
details: `Created game: ${game.title}`,
|
||||
@@ -66,8 +66,8 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
const game = await gameService.updateGame(id, request.body);
|
||||
if (game) {
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "game",
|
||||
resourceId: id,
|
||||
details: `Updated game: ${game.title}`,
|
||||
@@ -88,8 +88,8 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { id } = request.params;
|
||||
await gameService.deleteGame(id);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "game",
|
||||
resourceId: id,
|
||||
details: `Deleted game with ID: ${id}`,
|
||||
@@ -119,8 +119,8 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
const userId = request.user.id;
|
||||
const comment = await commentService.createCommentForGame(id, userId, request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "game",
|
||||
resourceId: id,
|
||||
details: `Added comment to game`,
|
||||
@@ -153,8 +153,8 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
const comment = await commentService.updateComment(commentId, request.body.content);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "game",
|
||||
resourceId: id,
|
||||
details: `Updated comment ${commentId} on game`,
|
||||
@@ -187,8 +187,8 @@ const gamesRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
await commentService.deleteComment(commentId);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_DELETE,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.commentDelete,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "game",
|
||||
resourceId: id,
|
||||
details: `Deleted comment ${commentId} from game`,
|
||||
|
||||
@@ -37,8 +37,8 @@ const mangaRoutes: FastifyPluginAsync = async (app) => {
|
||||
async (request) => {
|
||||
const manga = await mangaService.createManga(request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "manga",
|
||||
resourceId: manga.id,
|
||||
details: `Created manga: ${manga.title}`,
|
||||
@@ -62,8 +62,8 @@ const mangaRoutes: FastifyPluginAsync = async (app) => {
|
||||
const manga = await mangaService.updateManga(id, request.body);
|
||||
if (manga) {
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "manga",
|
||||
resourceId: id,
|
||||
details: `Updated manga: ${manga.title}`,
|
||||
@@ -83,8 +83,8 @@ const mangaRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { id } = request.params;
|
||||
await mangaService.deleteManga(id);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "manga",
|
||||
resourceId: id,
|
||||
details: `Deleted manga with ID: ${id}`,
|
||||
@@ -112,8 +112,8 @@ const mangaRoutes: FastifyPluginAsync = async (app) => {
|
||||
const userId = request.user.id;
|
||||
const comment = await commentService.createCommentForManga(id, userId, request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "manga",
|
||||
resourceId: id,
|
||||
details: `Added comment to manga`,
|
||||
@@ -145,8 +145,8 @@ const mangaRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
const comment = await commentService.updateComment(commentId, request.body.content);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "manga",
|
||||
resourceId: id,
|
||||
details: `Updated comment ${commentId} on manga`,
|
||||
@@ -178,8 +178,8 @@ const mangaRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
await commentService.deleteComment(commentId);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_DELETE,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.commentDelete,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "manga",
|
||||
resourceId: id,
|
||||
details: `Deleted comment ${commentId} from manga`,
|
||||
|
||||
@@ -46,8 +46,8 @@ const musicRoutes: FastifyPluginAsync = async (app) => {
|
||||
async (request) => {
|
||||
const music = await musicService.createMusic(request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "music",
|
||||
resourceId: music.id,
|
||||
details: `Created music: ${music.title}`,
|
||||
@@ -74,8 +74,8 @@ const musicRoutes: FastifyPluginAsync = async (app) => {
|
||||
const music = await musicService.updateMusic(id, request.body);
|
||||
if (music) {
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "music",
|
||||
resourceId: id,
|
||||
details: `Updated music: ${music.title}`,
|
||||
@@ -98,8 +98,8 @@ const musicRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { id } = request.params;
|
||||
await musicService.deleteMusic(id);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "music",
|
||||
resourceId: id,
|
||||
details: `Deleted music with ID: ${id}`,
|
||||
@@ -133,8 +133,8 @@ const musicRoutes: FastifyPluginAsync = async (app) => {
|
||||
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,
|
||||
action: AuditAction.commentCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "music",
|
||||
resourceId: id,
|
||||
details: `Added comment to music`,
|
||||
@@ -169,8 +169,8 @@ const musicRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
const comment = await commentService.updateComment(commentId, request.body.content);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "music",
|
||||
resourceId: id,
|
||||
details: `Updated comment ${commentId} on music`,
|
||||
@@ -205,8 +205,8 @@ const musicRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
await commentService.deleteComment(commentId);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_DELETE,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.commentDelete,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "music",
|
||||
resourceId: id,
|
||||
details: `Deleted comment ${commentId} from music`,
|
||||
|
||||
@@ -37,8 +37,8 @@ const showsRoutes: FastifyPluginAsync = async (app) => {
|
||||
async (request) => {
|
||||
const show = await showService.createShow(request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "show",
|
||||
resourceId: show.id,
|
||||
details: `Created show: ${show.title}`,
|
||||
@@ -62,8 +62,8 @@ const showsRoutes: FastifyPluginAsync = async (app) => {
|
||||
const show = await showService.updateShow(id, request.body);
|
||||
if (show) {
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "show",
|
||||
resourceId: id,
|
||||
details: `Updated show: ${show.title}`,
|
||||
@@ -83,8 +83,8 @@ const showsRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { id } = request.params;
|
||||
await showService.deleteShow(id);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "show",
|
||||
resourceId: id,
|
||||
details: `Deleted show with ID: ${id}`,
|
||||
@@ -112,8 +112,8 @@ const showsRoutes: FastifyPluginAsync = async (app) => {
|
||||
const userId = request.user.id;
|
||||
const comment = await commentService.createCommentForShow(id, userId, request.body);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "show",
|
||||
resourceId: id,
|
||||
details: `Added comment to show`,
|
||||
@@ -145,8 +145,8 @@ const showsRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
const comment = await commentService.updateComment(commentId, request.body.content);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_UPDATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.commentUpdate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "show",
|
||||
resourceId: id,
|
||||
details: `Updated comment ${commentId} on show`,
|
||||
@@ -178,8 +178,8 @@ const showsRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
await commentService.deleteComment(commentId);
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.COMMENT_DELETE,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.commentDelete,
|
||||
category: isAdmin && verification.comment?.userId !== userId ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "show",
|
||||
resourceId: id,
|
||||
details: `Deleted comment ${commentId} from show`,
|
||||
|
||||
@@ -85,8 +85,8 @@ export default async function (app: FastifyInstance): Promise<void> {
|
||||
);
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_CREATE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.entryCreate,
|
||||
category: AuditCategory.content,
|
||||
resourceType: "Suggestion",
|
||||
resourceId: suggestion.id,
|
||||
details: `Created ${suggestion.entityType} suggestion: ${suggestion.title}`,
|
||||
@@ -115,8 +115,8 @@ export default async function (app: FastifyInstance): Promise<void> {
|
||||
const suggestion = await SuggestionService.acceptSuggestion(id);
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.ADMIN,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.admin,
|
||||
resourceType: "Suggestion",
|
||||
resourceId: suggestion.id,
|
||||
details: `Accepted ${suggestion.entityType} suggestion: ${suggestion.title}`,
|
||||
@@ -146,8 +146,8 @@ export default async function (app: FastifyInstance): Promise<void> {
|
||||
const suggestion = await SuggestionService.acceptSuggestionWithEdits(id, editedData);
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.ADMIN,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.admin,
|
||||
resourceType: "Suggestion",
|
||||
resourceId: suggestion.id,
|
||||
details: `Accepted ${suggestion.entityType} suggestion with edits: ${suggestion.title}`,
|
||||
@@ -177,8 +177,8 @@ export default async function (app: FastifyInstance): Promise<void> {
|
||||
const suggestion = await SuggestionService.declineSuggestion(id, reason);
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_UPDATE,
|
||||
category: AuditCategory.ADMIN,
|
||||
action: AuditAction.entryUpdate,
|
||||
category: AuditCategory.admin,
|
||||
resourceType: "Suggestion",
|
||||
resourceId: suggestion.id,
|
||||
details: `Declined ${suggestion.entityType} suggestion: ${suggestion.title}${reason ? ` (Reason: ${reason})` : ""}`,
|
||||
@@ -209,8 +209,8 @@ export default async function (app: FastifyInstance): Promise<void> {
|
||||
const suggestion = await SuggestionService.deleteSuggestion(id, userId, isAdmin);
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.ENTRY_DELETE,
|
||||
category: isAdmin ? AuditCategory.ADMIN : AuditCategory.CONTENT,
|
||||
action: AuditAction.entryDelete,
|
||||
category: isAdmin ? AuditCategory.admin : AuditCategory.content,
|
||||
resourceType: "Suggestion",
|
||||
resourceId: suggestion.id,
|
||||
details: `Deleted ${suggestion.entityType} suggestion: ${suggestion.title}`,
|
||||
|
||||
@@ -54,8 +54,8 @@ const usersRoutes: FastifyPluginAsync = async (app) => {
|
||||
}
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.USER_BAN,
|
||||
category: AuditCategory.ADMIN,
|
||||
action: AuditAction.userBan,
|
||||
category: AuditCategory.admin,
|
||||
targetUserId: id,
|
||||
details: `Banned user: ${user.username}`,
|
||||
});
|
||||
@@ -78,8 +78,8 @@ const usersRoutes: FastifyPluginAsync = async (app) => {
|
||||
}
|
||||
|
||||
await AuditService.logFromRequest(request, {
|
||||
action: AuditAction.USER_UNBAN,
|
||||
category: AuditCategory.ADMIN,
|
||||
action: AuditAction.userUnban,
|
||||
category: AuditCategory.admin,
|
||||
targetUserId: id,
|
||||
details: `Unbanned user: ${user.username}`,
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ export const AuditService = {
|
||||
request: FastifyRequest,
|
||||
data: Omit<AuditLogData, "userId">
|
||||
) {
|
||||
const userId = (request.user as { id?: string } | undefined)?.id;
|
||||
const userId = ((request as any).user as { id?: string } | undefined)?.id;
|
||||
|
||||
return this.log(
|
||||
{
|
||||
|
||||
@@ -32,8 +32,8 @@ export class LikeService {
|
||||
});
|
||||
|
||||
await AuditService.logFromRequest(req, {
|
||||
action: AuditAction.UNLIKE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.unlike,
|
||||
category: AuditCategory.content,
|
||||
resourceType: entityType,
|
||||
resourceId: entityId,
|
||||
details: `Unliked ${entityType}`
|
||||
@@ -52,8 +52,8 @@ export class LikeService {
|
||||
});
|
||||
|
||||
await AuditService.logFromRequest(req, {
|
||||
action: AuditAction.LIKE,
|
||||
category: AuditCategory.CONTENT,
|
||||
action: AuditAction.like,
|
||||
category: AuditCategory.content,
|
||||
resourceType: entityType,
|
||||
resourceId: entityId,
|
||||
details: `Liked ${entityType}`
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
// Set required environment variables for tests
|
||||
process.env.JWT_SECRET = 'test-secret';
|
||||
process.env.DISCORD_CLIENT_ID = 'test-client-id';
|
||||
process.env.DISCORD_CLIENT_SECRET = 'test-client-secret';
|
||||
process.env.DOMAIN = 'http://localhost:3000';
|
||||
process.env.API_URL = 'http://localhost:3000/api';
|
||||
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test';
|
||||
process.env.BASE_URL = 'http://localhost:4200';
|
||||
process.env.NODE_ENV = 'test';
|
||||
@@ -206,8 +206,8 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
}
|
||||
|
||||
@if (showDeclineModal()) {
|
||||
<div class="modal-overlay" (click)="closeDeclineModal()">
|
||||
<div class="modal" (click)="$event.stopPropagation()">
|
||||
<div class="modal-overlay" (click)="closeDeclineModal()" (keyup.escape)="closeDeclineModal()" tabindex="0" role="button">
|
||||
<div class="modal" (click)="$event.stopPropagation()" (keyup)="$event.stopPropagation()" tabindex="-1">
|
||||
<h3>Decline Suggestion</h3>
|
||||
<p>Are you sure you want to decline "{{ decliningsuggestion()?.title }}"?</p>
|
||||
<div class="form-group">
|
||||
@@ -229,8 +229,8 @@ import { Suggestion, SuggestionStatus, SuggestionEntity } from '@library/shared-
|
||||
}
|
||||
|
||||
@if (showEditModal()) {
|
||||
<div class="modal-overlay" (click)="closeEditModal()">
|
||||
<div class="modal edit-modal" (click)="$event.stopPropagation()">
|
||||
<div class="modal-overlay" (click)="closeEditModal()" (keyup.escape)="closeEditModal()" tabindex="0" role="button">
|
||||
<div class="modal edit-modal" (click)="$event.stopPropagation()" (keyup)="$event.stopPropagation()" tabindex="-1">
|
||||
<h3>Review & Edit Before Accepting</h3>
|
||||
<p>Review and edit the details before adding to your collection.</p>
|
||||
|
||||
|
||||
@@ -105,8 +105,7 @@ import { Art, CreateArtDto, UpdateArtDto, Comment, SuggestionEntity, Link } from
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of newArt.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -123,8 +122,7 @@ import { Art, CreateArtDto, UpdateArtDto, Comment, SuggestionEntity, Link } from
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of newArt.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -280,8 +278,7 @@ import { Art, CreateArtDto, UpdateArtDto, Comment, SuggestionEntity, Link } from
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of editArt.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -298,8 +295,7 @@ import { Art, CreateArtDto, UpdateArtDto, Comment, SuggestionEntity, Link } from
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of editArt.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -401,6 +397,10 @@ import { Art, CreateArtDto, UpdateArtDto, Comment, SuggestionEntity, Link } from
|
||||
[alt]="art.description || art.title"
|
||||
class="art-image"
|
||||
(click)="openLightbox(art)"
|
||||
(keyup.enter)="openLightbox(art)"
|
||||
(keyup.space)="openLightbox(art)"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -536,8 +536,8 @@ import { Art, CreateArtDto, UpdateArtDto, Comment, SuggestionEntity, Link } from
|
||||
}
|
||||
|
||||
@if (lightboxArt()) {
|
||||
<div class="lightbox" (click)="closeLightbox()">
|
||||
<div class="lightbox-content" (click)="$event.stopPropagation()">
|
||||
<div class="lightbox" (click)="closeLightbox()" (keyup.escape)="closeLightbox()" tabindex="0" role="button">
|
||||
<div class="lightbox-content" (click)="$event.stopPropagation()" (keyup)="$event.stopPropagation()" tabindex="-1">
|
||||
<button class="lightbox-close" (click)="closeLightbox()">×</button>
|
||||
<img [src]="lightboxArt()!.imageUrl" [alt]="lightboxArt()!.description || lightboxArt()!.title">
|
||||
<div class="lightbox-info">
|
||||
|
||||
@@ -126,8 +126,7 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of newBook.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -144,8 +143,7 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of newBook.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -269,8 +267,7 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of editBook.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -287,8 +284,7 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of editBook.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -421,8 +417,7 @@ import { Book, BookStatus, CreateBookDto, UpdateBookDto, Comment, SuggestionEnti
|
||||
@if (showFilters()) {
|
||||
<div class="advanced-filters">
|
||||
<div class="filter-group">
|
||||
<label>Filter by Tags:</label>
|
||||
<div class="tags-filter">
|
||||
<div class="tags-filter" aria-label="Filter by Tags">
|
||||
@for (tag of allTags(); track tag) {
|
||||
<label class="tag-checkbox">
|
||||
<input
|
||||
|
||||
@@ -114,8 +114,7 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of newGame.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -132,8 +131,7 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of newGame.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -245,8 +243,7 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of editGame.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -263,8 +260,7 @@ import { Game, GameStatus, CreateGameDto, UpdateGameDto, Comment, SuggestionEnti
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of editGame.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
|
||||
@@ -115,8 +115,7 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of newManga.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -133,8 +132,7 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of newManga.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -247,8 +245,7 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of editManga.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -265,8 +262,7 @@ import { Manga, MangaStatus, CreateMangaDto, UpdateMangaDto, Comment, Suggestion
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of editManga.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
|
||||
@@ -124,8 +124,7 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of newMusic.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -142,8 +141,7 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of newMusic.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -265,8 +263,7 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of editMusicData.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -283,8 +280,7 @@ import { Music, MusicStatus, MusicType, CreateMusicDto, UpdateMusicDto, Comment,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of editMusicData.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
|
||||
@@ -113,8 +113,7 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of newShow.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -131,8 +130,7 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of newShow.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
@@ -243,8 +241,7 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tags</label>
|
||||
<div class="tags-input-container">
|
||||
<div class="tags-input-container" aria-label="Tags">
|
||||
@for (tag of editShow.tags; track tag; let i = $index) {
|
||||
<span class="tag">
|
||||
{{ tag }}
|
||||
@@ -261,8 +258,7 @@ import { Show, ShowStatus, ShowType, CreateShowDto, UpdateShowDto, Comment, Sugg
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>External Links</label>
|
||||
<div class="form-group" aria-label="External Links">
|
||||
<div class="links-list">
|
||||
@for (link of editShow.links; track link.url; let i = $index) {
|
||||
<div class="link-item">
|
||||
|
||||
@@ -19,14 +19,12 @@ import { environment } from '../../environments/environment';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
private router = inject(Router);
|
||||
private http = inject(HttpClient);
|
||||
|
||||
private isRefreshing = false;
|
||||
private refreshTokenSubject = new BehaviorSubject<boolean | null>(null);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private http: HttpClient
|
||||
) {}
|
||||
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
// Clone the request to add withCredentials
|
||||
const authReq = request.clone({
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Art, CreateArtDto, UpdateArtDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Art, CreateArtDto, UpdateArtDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ArtService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getAllArt(): Observable<Art[]> {
|
||||
return this.api.get<Art[]>('/art');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { Injectable, signal, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable, tap, catchError, switchMap, throwError, of } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
@@ -16,16 +16,14 @@ import { HttpClient } from '@angular/common/http';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
private api = inject(ApiService);
|
||||
private router = inject(Router);
|
||||
private http = inject(HttpClient);
|
||||
|
||||
private currentUser = signal<User | null>(null);
|
||||
public readonly user = this.currentUser.asReadonly();
|
||||
private refreshing = false;
|
||||
|
||||
constructor(
|
||||
private api: ApiService,
|
||||
private router: Router,
|
||||
private http: HttpClient
|
||||
) {}
|
||||
|
||||
login(): void {
|
||||
// Redirect to API login endpoint
|
||||
window.location.href = `${environment.apiUrl}/auth/login`;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Book, CreateBookDto, UpdateBookDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Book, CreateBookDto, UpdateBookDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BooksService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getAllBooks(): Observable<Book[]> {
|
||||
return this.api.get<Book[]>('/books');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Comment, CreateCommentDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Comment, CreateCommentDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CommentsService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getCommentsForGame(gameId: string): Observable<Comment[]> {
|
||||
return this.api.get<Comment[]>(`/games/${gameId}/comments`);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Game, CreateGameDto, UpdateGameDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Game, CreateGameDto, UpdateGameDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GamesService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getAllGames(): Observable<Game[]> {
|
||||
return this.api.get<Game[]>('/games');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Manga, CreateMangaDto, UpdateMangaDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Manga, CreateMangaDto, UpdateMangaDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MangaService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getAllManga(): Observable<Manga[]> {
|
||||
return this.api.get<Manga[]>('/manga');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Music, CreateMusicDto, UpdateMusicDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Music, CreateMusicDto, UpdateMusicDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MusicService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getAllMusic(): Observable<Music[]> {
|
||||
return this.api.get<Music[]>('/music');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { Show, CreateShowDto, UpdateShowDto } from '@library/shared-types';
|
||||
@@ -13,7 +13,8 @@ import { Show, CreateShowDto, UpdateShowDto } from '@library/shared-types';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ShowsService {
|
||||
constructor(private api: ApiService) {}
|
||||
private api = inject(ApiService);
|
||||
|
||||
|
||||
getAllShows(): Observable<Show[]> {
|
||||
return this.api.get<Show[]>('/shows');
|
||||
|
||||
+11
-11
@@ -1,17 +1,17 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
export * from "./lib/game.types";
|
||||
export * from "./lib/book.types";
|
||||
export * from "./lib/music.types";
|
||||
export * from "./lib/art.types";
|
||||
export * from "./lib/show.types";
|
||||
export * from "./lib/manga.types";
|
||||
export type * from "./lib/auth.types";
|
||||
export * from "./lib/comment.types";
|
||||
export type * from "./lib/art.types";
|
||||
export * from "./lib/audit.types";
|
||||
export type * from "./lib/auth.types";
|
||||
export * from "./lib/book.types";
|
||||
export type * from "./lib/comment.types";
|
||||
export type * from "./lib/common.types";
|
||||
export * from "./lib/game.types";
|
||||
export type * from "./lib/like.types";
|
||||
export * from "./lib/manga.types";
|
||||
export * from "./lib/music.types";
|
||||
export * from "./lib/show.types";
|
||||
export * from "./lib/suggestion.types";
|
||||
export * from "./lib/common.types";
|
||||
export * from "./lib/like.types";
|
||||
@@ -1,30 +1,33 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import { Link } from "./common.types";
|
||||
import type { Link } from "./common.types";
|
||||
|
||||
export interface Art {
|
||||
interface Art {
|
||||
id: string;
|
||||
title: string;
|
||||
artist: string;
|
||||
description?: string;
|
||||
imageUrl: string;
|
||||
tags: string[];
|
||||
links: Link[];
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
dateAdded: Date;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateArtDto {
|
||||
interface CreateArtDto {
|
||||
title: string;
|
||||
artist: string;
|
||||
description?: string;
|
||||
imageUrl: string;
|
||||
tags?: string[];
|
||||
links?: Link[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
}
|
||||
|
||||
export interface UpdateArtDto extends Partial<CreateArtDto> {}
|
||||
type UpdateArtDto = Partial<CreateArtDto>;
|
||||
|
||||
export type { Art, CreateArtDto, UpdateArtDto };
|
||||
|
||||
@@ -1,36 +1,42 @@
|
||||
export enum AuditAction {
|
||||
LOGIN = "LOGIN",
|
||||
LOGOUT = "LOGOUT",
|
||||
LOGIN_FAILED = "LOGIN_FAILED",
|
||||
COMMENT_CREATE = "COMMENT_CREATE",
|
||||
COMMENT_UPDATE = "COMMENT_UPDATE",
|
||||
COMMENT_DELETE = "COMMENT_DELETE",
|
||||
ENTRY_CREATE = "ENTRY_CREATE",
|
||||
ENTRY_UPDATE = "ENTRY_UPDATE",
|
||||
ENTRY_DELETE = "ENTRY_DELETE",
|
||||
LIKE = "LIKE",
|
||||
UNLIKE = "UNLIKE",
|
||||
USER_BAN = "USER_BAN",
|
||||
USER_UNBAN = "USER_UNBAN",
|
||||
RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED",
|
||||
CSRF_VALIDATION_FAILED = "CSRF_VALIDATION_FAILED",
|
||||
UNAUTHORIZED_ACCESS = "UNAUTHORIZED_ACCESS",
|
||||
/**
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
enum AuditAction {
|
||||
login = "LOGIN",
|
||||
logout = "LOGOUT",
|
||||
loginFailed = "LOGIN_FAILED",
|
||||
commentCreate = "COMMENT_CREATE",
|
||||
commentUpdate = "COMMENT_UPDATE",
|
||||
commentDelete = "COMMENT_DELETE",
|
||||
entryCreate = "ENTRY_CREATE",
|
||||
entryUpdate = "ENTRY_UPDATE",
|
||||
entryDelete = "ENTRY_DELETE",
|
||||
like = "LIKE",
|
||||
unlike = "UNLIKE",
|
||||
userBan = "USER_BAN",
|
||||
userUnban = "USER_UNBAN",
|
||||
rateLimitExceeded = "RATE_LIMIT_EXCEEDED",
|
||||
csrfValidationFailed = "CSRF_VALIDATION_FAILED",
|
||||
unauthorizedAccess = "UNAUTHORIZED_ACCESS",
|
||||
}
|
||||
|
||||
export enum AuditCategory {
|
||||
AUTH = "AUTH",
|
||||
CONTENT = "CONTENT",
|
||||
ADMIN = "ADMIN",
|
||||
SECURITY = "SECURITY",
|
||||
enum AuditCategory {
|
||||
auth = "AUTH",
|
||||
content = "CONTENT",
|
||||
admin = "ADMIN",
|
||||
security = "SECURITY",
|
||||
}
|
||||
|
||||
export interface AuditLogUser {
|
||||
interface AuditLogUser {
|
||||
id: string;
|
||||
username: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
export interface AuditLog {
|
||||
interface AuditLog {
|
||||
id: string;
|
||||
action: AuditAction;
|
||||
category: AuditCategory;
|
||||
@@ -46,7 +52,7 @@ export interface AuditLog {
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AuditLogFilters {
|
||||
interface AuditLogFilters {
|
||||
action?: AuditAction;
|
||||
category?: AuditCategory;
|
||||
userId?: string;
|
||||
@@ -56,3 +62,11 @@ export interface AuditLogFilters {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export {
|
||||
AuditAction,
|
||||
AuditCategory,
|
||||
type AuditLog,
|
||||
type AuditLogFilters,
|
||||
type AuditLogUser,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export interface User {
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
@@ -18,7 +18,8 @@ export interface User {
|
||||
isStaff: boolean;
|
||||
}
|
||||
|
||||
export interface JwtPayload {
|
||||
interface JwtPayload {
|
||||
|
||||
/**
|
||||
* User id.
|
||||
*/
|
||||
@@ -30,7 +31,9 @@ export interface JwtPayload {
|
||||
exp?: number;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
interface AuthResponse {
|
||||
accessToken: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export type { AuthResponse, JwtPayload, User };
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export enum BookStatus {
|
||||
import type { Link } from "./common.types";
|
||||
|
||||
enum BookStatus {
|
||||
reading = "READING",
|
||||
finished = "FINISHED",
|
||||
toRead = "TO_READ",
|
||||
}
|
||||
|
||||
import { Link } from "./common.types";
|
||||
|
||||
export interface Book {
|
||||
interface Book {
|
||||
id: string;
|
||||
title: string;
|
||||
author: string;
|
||||
@@ -23,13 +23,13 @@ export interface Book {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags: string[];
|
||||
links: Link[];
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateBookDto {
|
||||
interface CreateBookDto {
|
||||
title: string;
|
||||
author: string;
|
||||
isbn?: string;
|
||||
@@ -37,10 +37,12 @@ export interface CreateBookDto {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags?: string[];
|
||||
links?: Link[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
}
|
||||
|
||||
export interface UpdateBookDto extends Partial<CreateBookDto> {
|
||||
interface UpdateBookDto extends Partial<CreateBookDto> {
|
||||
dateFinished?: Date;
|
||||
}
|
||||
|
||||
export { type Book, BookStatus, type CreateBookDto, type UpdateBookDto };
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export interface CommentUser {
|
||||
interface CommentUser {
|
||||
id: string;
|
||||
username: string;
|
||||
avatar?: string;
|
||||
@@ -14,7 +14,7 @@ export interface CommentUser {
|
||||
isStaff?: boolean;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
interface Comment {
|
||||
id: string;
|
||||
content: string;
|
||||
rawContent?: string;
|
||||
@@ -30,6 +30,8 @@ export interface Comment {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateCommentDto {
|
||||
interface CreateCommentDto {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type { Comment, CommentUser, CreateCommentDto };
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export interface Link {
|
||||
title: string;
|
||||
url: string;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export enum GameStatus {
|
||||
import type { Link } from "./common.types";
|
||||
|
||||
enum GameStatus {
|
||||
playing = "PLAYING",
|
||||
completed = "COMPLETED",
|
||||
backlog = "BACKLOG",
|
||||
}
|
||||
|
||||
import { Link } from "./common.types";
|
||||
|
||||
export interface Game {
|
||||
interface Game {
|
||||
id: string;
|
||||
title: string;
|
||||
platform?: string;
|
||||
@@ -22,23 +22,25 @@ export interface Game {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags: string[];
|
||||
links: Link[];
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateGameDto {
|
||||
interface CreateGameDto {
|
||||
title: string;
|
||||
platform?: string;
|
||||
status: GameStatus;
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags?: string[];
|
||||
links?: Link[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
}
|
||||
|
||||
export interface UpdateGameDto extends Partial<CreateGameDto> {
|
||||
interface UpdateGameDto extends Partial<CreateGameDto> {
|
||||
dateCompleted?: Date;
|
||||
}
|
||||
|
||||
export { type CreateGameDto, type Game, GameStatus, type UpdateGameDto };
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export interface Like {
|
||||
interface Like {
|
||||
id: string;
|
||||
userId: string;
|
||||
entityType: 'book' | 'game' | 'show' | 'manga' | 'music' | 'art';
|
||||
entityType: "book" | "game" | "show" | "manga" | "music" | "art";
|
||||
entityId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export type CreateLikeDto = Pick<Like, 'entityType' | 'entityId'>;
|
||||
type CreateLikeDto = Pick<Like, "entityType" | "entityId">;
|
||||
|
||||
export interface LikeCountDto {
|
||||
interface LikeCountDto {
|
||||
entityId: string;
|
||||
entityType: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface LikedItemDto {
|
||||
interface LikedItemDto {
|
||||
like: Like;
|
||||
item: any; // This will be the actual entity (Book, Game, etc.)
|
||||
|
||||
/**
|
||||
* This will be the actual entity (Book, Game, etc.).
|
||||
*/
|
||||
item: unknown;
|
||||
}
|
||||
|
||||
// Response types
|
||||
export interface LikeResponse {
|
||||
interface LikeResponse {
|
||||
liked: boolean;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export type { CreateLikeDto, Like, LikeCountDto, LikedItemDto, LikeResponse };
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export enum MangaStatus {
|
||||
import type { Link } from "./common.types";
|
||||
|
||||
enum MangaStatus {
|
||||
reading = "READING",
|
||||
completed = "COMPLETED",
|
||||
wantToRead = "WANT_TO_READ",
|
||||
}
|
||||
|
||||
import { Link } from "./common.types";
|
||||
|
||||
export interface Manga {
|
||||
interface Manga {
|
||||
id: string;
|
||||
title: string;
|
||||
author: string;
|
||||
@@ -22,23 +22,26 @@ export interface Manga {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags: string[];
|
||||
links: Link[];
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateMangaDto {
|
||||
interface CreateMangaDto {
|
||||
title: string;
|
||||
author: string;
|
||||
status: MangaStatus;
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags?: string[];
|
||||
links?: Link[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
}
|
||||
|
||||
export interface UpdateMangaDto extends Partial<CreateMangaDto> {
|
||||
interface UpdateMangaDto extends Partial<CreateMangaDto> {
|
||||
dateCompleted?: Date;
|
||||
}
|
||||
|
||||
export { MangaStatus };
|
||||
export type { CreateMangaDto, Manga, UpdateMangaDto };
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export enum MusicType {
|
||||
import type { Link } from "./common.types";
|
||||
|
||||
enum MusicType {
|
||||
album = "ALBUM",
|
||||
single = "SINGLE",
|
||||
ep = "EP",
|
||||
}
|
||||
|
||||
export enum MusicStatus {
|
||||
enum MusicStatus {
|
||||
listening = "LISTENING",
|
||||
completed = "COMPLETED",
|
||||
wantToListen = "WANT_TO_LISTEN",
|
||||
}
|
||||
|
||||
import { Link } from "./common.types";
|
||||
|
||||
export interface Music {
|
||||
interface Music {
|
||||
id: string;
|
||||
title: string;
|
||||
artist: string;
|
||||
@@ -29,13 +29,13 @@ export interface Music {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverArt?: string;
|
||||
tags: string[];
|
||||
links: Link[];
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateMusicDto {
|
||||
interface CreateMusicDto {
|
||||
title: string;
|
||||
artist: string;
|
||||
type: MusicType;
|
||||
@@ -43,10 +43,13 @@ export interface CreateMusicDto {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverArt?: string;
|
||||
tags?: string[];
|
||||
links?: Link[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
}
|
||||
|
||||
export interface UpdateMusicDto extends Partial<CreateMusicDto> {
|
||||
interface UpdateMusicDto extends Partial<CreateMusicDto> {
|
||||
dateCompleted?: Date;
|
||||
}
|
||||
|
||||
export { MusicStatus, MusicType };
|
||||
export type { CreateMusicDto, Music, UpdateMusicDto };
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
/**
|
||||
* @copyright 2026 NHCarrigan
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
export enum ShowType {
|
||||
import type { Link } from "./common.types";
|
||||
|
||||
enum ShowType {
|
||||
tvSeries = "TV_SERIES",
|
||||
anime = "ANIME",
|
||||
film = "FILM",
|
||||
documentary = "DOCUMENTARY",
|
||||
}
|
||||
|
||||
export enum ShowStatus {
|
||||
enum ShowStatus {
|
||||
watching = "WATCHING",
|
||||
completed = "COMPLETED",
|
||||
wantToWatch = "WANT_TO_WATCH",
|
||||
}
|
||||
|
||||
import { Link } from "./common.types";
|
||||
|
||||
export interface Show {
|
||||
interface Show {
|
||||
id: string;
|
||||
title: string;
|
||||
type: ShowType;
|
||||
@@ -29,23 +29,26 @@ export interface Show {
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags: string[];
|
||||
links: Link[];
|
||||
tags: Array<string>;
|
||||
links: Array<Link>;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateShowDto {
|
||||
interface CreateShowDto {
|
||||
title: string;
|
||||
type: ShowType;
|
||||
status: ShowStatus;
|
||||
rating?: number;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
tags?: string[];
|
||||
links?: Link[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<Link>;
|
||||
}
|
||||
|
||||
export interface UpdateShowDto extends Partial<CreateShowDto> {
|
||||
interface UpdateShowDto extends Partial<CreateShowDto> {
|
||||
dateCompleted?: Date;
|
||||
}
|
||||
|
||||
export { ShowStatus, ShowType };
|
||||
export type { CreateShowDto, Show, UpdateShowDto };
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
import type { CreateGameDto } from "./game.types";
|
||||
import type { CreateBookDto } from "./book.types";
|
||||
import type { CreateMusicDto } from "./music.types";
|
||||
/**
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
import type { CreateArtDto } from "./art.types";
|
||||
import type { CreateShowDto } from "./show.types";
|
||||
import type { CreateBookDto } from "./book.types";
|
||||
import type { CreateGameDto } from "./game.types";
|
||||
import type { CreateMangaDto } from "./manga.types";
|
||||
import type { CreateMusicDto } from "./music.types";
|
||||
import type { CreateShowDto } from "./show.types";
|
||||
|
||||
export enum SuggestionEntity {
|
||||
GAME = "GAME",
|
||||
BOOK = "BOOK",
|
||||
MUSIC = "MUSIC",
|
||||
ART = "ART",
|
||||
SHOW = "SHOW",
|
||||
MANGA = "MANGA",
|
||||
enum SuggestionEntity {
|
||||
game = "GAME",
|
||||
book = "BOOK",
|
||||
music = "MUSIC",
|
||||
art = "ART",
|
||||
show = "SHOW",
|
||||
manga = "MANGA",
|
||||
}
|
||||
|
||||
export enum SuggestionStatus {
|
||||
UNREVIEWED = "UNREVIEWED",
|
||||
ACCEPTED = "ACCEPTED",
|
||||
DECLINED = "DECLINED",
|
||||
enum SuggestionStatus {
|
||||
unreviewed = "UNREVIEWED",
|
||||
accepted = "ACCEPTED",
|
||||
declined = "DECLINED",
|
||||
}
|
||||
|
||||
export interface SuggestionUser {
|
||||
interface SuggestionUser {
|
||||
id: string;
|
||||
username: string;
|
||||
avatar?: string;
|
||||
@@ -30,7 +36,7 @@ export interface SuggestionUser {
|
||||
isStaff: boolean;
|
||||
}
|
||||
|
||||
export interface Suggestion {
|
||||
interface Suggestion {
|
||||
id: string;
|
||||
userId: string;
|
||||
user: SuggestionUser;
|
||||
@@ -48,16 +54,16 @@ export interface Suggestion {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateGameSuggestionDto {
|
||||
entityType: SuggestionEntity.GAME;
|
||||
interface CreateGameSuggestionDto {
|
||||
entityType: SuggestionEntity.game;
|
||||
title: string;
|
||||
platform?: string;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
}
|
||||
|
||||
export interface CreateBookSuggestionDto {
|
||||
entityType: SuggestionEntity.BOOK;
|
||||
interface CreateBookSuggestionDto {
|
||||
entityType: SuggestionEntity.book;
|
||||
title: string;
|
||||
author: string;
|
||||
isbn?: string;
|
||||
@@ -65,8 +71,8 @@ export interface CreateBookSuggestionDto {
|
||||
coverImage?: string;
|
||||
}
|
||||
|
||||
export interface CreateMusicSuggestionDto {
|
||||
entityType: SuggestionEntity.MUSIC;
|
||||
interface CreateMusicSuggestionDto {
|
||||
entityType: SuggestionEntity.music;
|
||||
title: string;
|
||||
artist: string;
|
||||
type: string;
|
||||
@@ -74,31 +80,31 @@ export interface CreateMusicSuggestionDto {
|
||||
coverArt?: string;
|
||||
}
|
||||
|
||||
export interface CreateArtSuggestionDto {
|
||||
entityType: SuggestionEntity.ART;
|
||||
interface CreateArtSuggestionDto {
|
||||
entityType: SuggestionEntity.art;
|
||||
title: string;
|
||||
artist: string;
|
||||
description?: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
export interface CreateShowSuggestionDto {
|
||||
entityType: SuggestionEntity.SHOW;
|
||||
interface CreateShowSuggestionDto {
|
||||
entityType: SuggestionEntity.show;
|
||||
title: string;
|
||||
type: string;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
}
|
||||
|
||||
export interface CreateMangaSuggestionDto {
|
||||
entityType: SuggestionEntity.MANGA;
|
||||
interface CreateMangaSuggestionDto {
|
||||
entityType: SuggestionEntity.manga;
|
||||
title: string;
|
||||
author: string;
|
||||
notes?: string;
|
||||
coverImage?: string;
|
||||
}
|
||||
|
||||
export type CreateSuggestionDto =
|
||||
type CreateSuggestionDto =
|
||||
| CreateGameSuggestionDto
|
||||
| CreateBookSuggestionDto
|
||||
| CreateMusicSuggestionDto
|
||||
@@ -106,11 +112,11 @@ export type CreateSuggestionDto =
|
||||
| CreateShowSuggestionDto
|
||||
| CreateMangaSuggestionDto;
|
||||
|
||||
export interface DeclineSuggestionDto {
|
||||
interface DeclineSuggestionDto {
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface AcceptWithEditsDto {
|
||||
interface AcceptWithEditsDto {
|
||||
title?: string;
|
||||
platform?: string;
|
||||
author?: string;
|
||||
@@ -122,6 +128,21 @@ export interface AcceptWithEditsDto {
|
||||
coverImage?: string;
|
||||
coverArt?: string;
|
||||
imageUrl?: string;
|
||||
tags?: string[];
|
||||
tags?: Array<string>;
|
||||
links?: Array<{ label: string; url: string }>;
|
||||
}
|
||||
|
||||
export { SuggestionEntity, SuggestionStatus };
|
||||
export type {
|
||||
AcceptWithEditsDto,
|
||||
CreateArtSuggestionDto,
|
||||
CreateBookSuggestionDto,
|
||||
CreateGameSuggestionDto,
|
||||
CreateMangaSuggestionDto,
|
||||
CreateMusicSuggestionDto,
|
||||
CreateShowSuggestionDto,
|
||||
CreateSuggestionDto,
|
||||
DeclineSuggestionDto,
|
||||
Suggestion,
|
||||
SuggestionUser,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user