import { prisma } from "../lib/prisma"; import type { Suggestion, SuggestionStatus, SuggestionEntity, CreateSuggestionDto, } from "@library/shared-types"; import { GameStatus, BookStatus, MusicType, MusicStatus, ShowType, ShowStatus, MangaStatus, } from "@library/shared-types"; import { GameService } from "./game.service"; import { BookService } from "./book.service"; import { MusicService } from "./music.service"; import { ArtService } from "./art.service"; import { ShowService } from "./show.service"; import { MangaService } from "./manga.service"; const gameService = new GameService(); const bookService = new BookService(); const musicService = new MusicService(); const artService = new ArtService(); const showService = new ShowService(); const mangaService = new MangaService(); interface SuggestionUser { id: string; username: string; avatar: string | null; inDiscord: boolean; isVip: boolean; isMod: boolean; isStaff: boolean; } function mapUser(user: { id: string; username: string; avatar: string | null; inDiscord: boolean; isVip: boolean; isMod: boolean; isStaff: boolean; }): SuggestionUser { return { id: user.id, username: user.username, avatar: user.avatar, inDiscord: user.inDiscord, isVip: user.isVip, isMod: user.isMod, isStaff: user.isStaff, }; } function mapSuggestion(suggestion: { id: string; userId: string; user: { id: string; username: string; avatar: string | null; inDiscord: boolean; isVip: boolean; isMod: boolean; isStaff: boolean; }; entityType: string; status: string; declineReason: string | null; title: string; gameData: unknown; bookData: unknown; musicData: unknown; artData: unknown; showData: unknown; mangaData: unknown; createdAt: Date; updatedAt: Date; }): Suggestion { return { id: suggestion.id, userId: suggestion.userId, user: mapUser(suggestion.user) as Suggestion["user"], entityType: suggestion.entityType as SuggestionEntity, status: suggestion.status as SuggestionStatus, declineReason: suggestion.declineReason ?? undefined, title: suggestion.title, gameData: suggestion.gameData as Suggestion["gameData"], bookData: suggestion.bookData as Suggestion["bookData"], musicData: suggestion.musicData as Suggestion["musicData"], artData: suggestion.artData as Suggestion["artData"], showData: suggestion.showData as Suggestion["showData"], mangaData: suggestion.mangaData as Suggestion["mangaData"], createdAt: suggestion.createdAt, updatedAt: suggestion.updatedAt, }; } function getMusicType(type?: string): MusicType { switch (type) { case "ALBUM": return MusicType.album; case "SINGLE": return MusicType.single; case "EP": return MusicType.ep; default: return MusicType.album; } } function getShowType(type?: string): ShowType { switch (type) { case "TV_SERIES": return ShowType.tvSeries; case "ANIME": return ShowType.anime; case "FILM": return ShowType.film; case "DOCUMENTARY": return ShowType.documentary; default: return ShowType.tvSeries; } } export const SuggestionService = { async getAllSuggestions(filters?: { status?: SuggestionStatus; entityType?: SuggestionEntity; }): Promise { const suggestions = await prisma.suggestion.findMany({ where: { ...(filters?.status && { status: filters.status }), ...(filters?.entityType && { entityType: filters.entityType }), }, include: { user: true, }, orderBy: { createdAt: "desc", }, }); return suggestions.map(mapSuggestion); }, async getUserSuggestions(userId: string): Promise { const suggestions = await prisma.suggestion.findMany({ where: { userId, }, include: { user: true, }, orderBy: { createdAt: "desc", }, }); return suggestions.map(mapSuggestion); }, async getSuggestionById(id: string): Promise { const suggestion = await prisma.suggestion.findUnique({ where: { id }, include: { user: true, }, }); if (!suggestion) { return null; } return mapSuggestion(suggestion); }, async createSuggestion( userId: string, data: CreateSuggestionDto ): Promise { const pendingCount = await prisma.suggestion.count({ where: { userId, status: "UNREVIEWED", }, }); if (pendingCount >= 5) { throw new Error("You can only have 5 pending suggestions at a time"); } const entityDataField = `${data.entityType.toLowerCase()}Data`; const suggestionData: Record = { userId, entityType: data.entityType, title: data.title, }; // Extract the data without entityType and title const { entityType: _et, title: _t, ...entityData } = data; suggestionData[entityDataField] = entityData; const suggestion = await prisma.suggestion.create({ data: suggestionData as Parameters[0]["data"], include: { user: true, }, }); return mapSuggestion(suggestion); }, async acceptSuggestion(id: string): Promise { const suggestion = await prisma.suggestion.findUnique({ where: { id }, include: { user: true }, }); if (!suggestion) { throw new Error("Suggestion not found"); } if (suggestion.status !== "UNREVIEWED") { throw new Error("Suggestion has already been reviewed"); } // Create the entity based on type with "Want to X" status switch (suggestion.entityType) { case "GAME": { const gameData = suggestion.gameData as { platform?: string; notes?: string; coverImage?: string; } | null; await gameService.createGame({ title: suggestion.title, platform: gameData?.platform, status: GameStatus.backlog, notes: gameData?.notes, coverImage: gameData?.coverImage, }); break; } case "BOOK": { const bookData = suggestion.bookData as { author: string; isbn?: string; notes?: string; coverImage?: string; } | null; await bookService.createBook({ title: suggestion.title, author: bookData?.author ?? "Unknown", isbn: bookData?.isbn, status: BookStatus.toRead, notes: bookData?.notes, coverImage: bookData?.coverImage, }); break; } case "MUSIC": { const musicData = suggestion.musicData as { artist: string; type: string; notes?: string; coverArt?: string; } | null; await musicService.createMusic({ title: suggestion.title, artist: musicData?.artist ?? "Unknown", type: getMusicType(musicData?.type), status: MusicStatus.wantToListen, notes: musicData?.notes, coverArt: musicData?.coverArt, }); break; } case "ART": { const artData = suggestion.artData as { artist: string; description?: string; imageUrl: string; } | null; await artService.createArt({ title: suggestion.title, artist: artData?.artist ?? "Unknown", description: artData?.description, imageUrl: artData?.imageUrl ?? "", }); break; } case "SHOW": { const showData = suggestion.showData as { type: string; notes?: string; coverImage?: string; } | null; await showService.createShow({ title: suggestion.title, type: getShowType(showData?.type), status: ShowStatus.wantToWatch, notes: showData?.notes, coverImage: showData?.coverImage, }); break; } case "MANGA": { const mangaData = suggestion.mangaData as { author: string; notes?: string; coverImage?: string; } | null; await mangaService.createManga({ title: suggestion.title, author: mangaData?.author ?? "Unknown", status: MangaStatus.wantToRead, notes: mangaData?.notes, coverImage: mangaData?.coverImage, }); break; } } // Update suggestion status const updatedSuggestion = await prisma.suggestion.update({ where: { id }, data: { status: "ACCEPTED" }, include: { user: true }, }); return mapSuggestion(updatedSuggestion); }, async declineSuggestion(id: string, reason?: string): Promise { const suggestion = await prisma.suggestion.findUnique({ where: { id }, }); if (!suggestion) { throw new Error("Suggestion not found"); } if (suggestion.status !== "UNREVIEWED") { throw new Error("Suggestion has already been reviewed"); } const updatedSuggestion = await prisma.suggestion.update({ where: { id }, data: { status: "DECLINED", declineReason: reason, }, include: { user: true }, }); return mapSuggestion(updatedSuggestion); }, };