import type { PrismaClient, Wish, WishStatus } from '@prisma/client'; import { ConflictError, ForbiddenError, NotFoundError, } from '../../utils/errors.js'; import type { CreateWishInput, UpdateWishInput } from '@family-wishlist/shared'; type Status = 'active' | 'archived' | 'completed' | 'deleted'; const statusMap: Record = { active: 'ACTIVE', archived: 'ARCHIVED', completed: 'COMPLETED', deleted: 'DELETED', }; export class WishesService { constructor(private readonly prisma: PrismaClient) {} list(userId: string, status: Status): Promise { return this.prisma.wish.findMany({ where: { userId, status: statusMap[status] }, orderBy: [{ status: 'asc' }, { createdAt: 'desc' }], }); } async getOwned(userId: string, id: string): Promise { const wish = await this.prisma.wish.findUnique({ where: { id } }); if (!wish) throw new NotFoundError('Wish'); if (wish.userId !== userId) throw new ForbiddenError(); return wish; } create(userId: string, input: CreateWishInput): Promise { return this.prisma.wish.create({ data: { userId, title: input.title, price: input.price ?? null, currency: input.currency ?? 'RUB', url: input.url ?? null, comment: input.comment ?? null, }, }); } async update(userId: string, id: string, input: UpdateWishInput): Promise { await this.getOwned(userId, id); const data: Record = {}; if (input.title !== undefined) data.title = input.title; if (input.price !== undefined) data.price = input.price ?? null; if (input.currency !== undefined) data.currency = input.currency ?? 'RUB'; if (input.url !== undefined) data.url = input.url ?? null; if (input.comment !== undefined) data.comment = input.comment ?? null; return this.prisma.wish.update({ where: { id }, data }); } async archive(userId: string, id: string): Promise { const wish = await this.getOwned(userId, id); if (wish.status === 'ARCHIVED') return wish; if (wish.status === 'DELETED') { throw new ConflictError('Cannot archive a deleted wish; restore it first'); } return this.prisma.wish.update({ where: { id }, data: { status: 'ARCHIVED', archivedAt: new Date(), completedAt: null, deletedAt: null }, }); } async complete(userId: string, id: string): Promise { const wish = await this.getOwned(userId, id); if (wish.status === 'COMPLETED') return wish; if (wish.status === 'DELETED') { throw new ConflictError('Cannot complete a deleted wish; restore it first'); } return this.prisma.wish.update({ where: { id }, data: { status: 'COMPLETED', completedAt: new Date(), archivedAt: null, deletedAt: null }, }); } async softDelete(userId: string, id: string): Promise { const wish = await this.getOwned(userId, id); if (wish.status === 'DELETED') return wish; return this.prisma.wish.update({ where: { id }, data: { status: 'DELETED', deletedAt: new Date() }, }); } async restore(userId: string, id: string): Promise { const wish = await this.getOwned(userId, id); if (wish.status === 'ACTIVE') return wish; return this.prisma.wish.update({ where: { id }, data: { status: 'ACTIVE', archivedAt: null, completedAt: null, deletedAt: null }, }); } async duplicate(userId: string, id: string): Promise { const source = await this.getOwned(userId, id); return this.prisma.wish.create({ data: { userId, title: source.title, price: source.price, currency: source.currency, url: source.url, comment: source.comment, imageUrl: source.imageSource === 'UPLOADED' ? null : source.imageUrl, imageSource: source.imageSource === 'UPLOADED' ? 'DEFAULT' : source.imageSource, sourceWishId: source.id, }, }); } }