118 lines
3.9 KiB
TypeScript
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,
|
|
},
|
|
});
|
|
}
|
|
}
|