Files
family_wishlist/apps/backend/src/modules/wishes/wishes.service.ts

118 lines
3.9 KiB
TypeScript

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<Status, WishStatus> = {
active: 'ACTIVE',
archived: 'ARCHIVED',
completed: 'COMPLETED',
deleted: 'DELETED',
};
export class WishesService {
constructor(private readonly prisma: PrismaClient) {}
list(userId: string, status: Status): Promise<Wish[]> {
return this.prisma.wish.findMany({
where: { userId, status: statusMap[status] },
orderBy: [{ status: 'asc' }, { createdAt: 'desc' }],
});
}
async getOwned(userId: string, id: string): Promise<Wish> {
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<Wish> {
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<Wish> {
await this.getOwned(userId, id);
const data: Record<string, unknown> = {};
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<Wish> {
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<Wish> {
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<Wish> {
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<Wish> {
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<Wish> {
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,
},
});
}
}