import { Injectable } from '@nestjs/common'; import { DatabaseService } from '../database/database.service'; import type { Prisma } from '../generated/prisma/client'; import { AccessStore } from '../identity/access.store'; import { recordAudit } from '../identity/audit'; import type { Principal } from '../identity/identity.types'; import { AppError } from '../common/errors/app-error'; import type { AddressInput } from './address.schema'; async function ensureDefault(tx: Prisma.TransactionClient, userId: string) { if (await tx.address.count({ where: { userId, isDefault: true } })) return; const first = await tx.address.findFirst({ where: { userId }, orderBy: [{ createdAt: 'asc' }, { id: 'asc' }], }); if (first) await tx.address.update({ where: { id: first.id }, data: { isDefault: true }, }); } @Injectable() export class AddressStore { constructor( private readonly db: DatabaseService, private readonly access: AccessStore, ) {} list(actor: Principal) { return this.db.address.findMany({ where: { userId: actor.userId, organizationId: actor.organizationId }, orderBy: [{ isDefault: 'desc' }, { createdAt: 'asc' }, { id: 'asc' }], take: 20, }); } save(actor: Principal, input: AddressInput, id?: string) { return this.access.mutate(actor, null, async (tx) => { const where = { userId: actor.userId, organizationId: actor.organizationId, }; if (id && !(await tx.address.findFirst({ where: { ...where, id } }))) throw new AppError('ADDRESS_NOT_FOUND'); if (!id && (await tx.address.count({ where })) >= 20) throw new AppError('ADDRESS_LIMIT'); if (input.isDefault) await tx.address.updateMany({ where, data: { isDefault: false } }); const address = id ? await tx.address.update({ where: { id }, data: input }) : await tx.address.create({ data: { ...where, ...input } }); await ensureDefault(tx, actor.userId); await recordAudit( tx, actor.organizationId, actor.userId, id ? 'address.updated' : 'address.created', address.id, ); return tx.address.findUniqueOrThrow({ where: { id: address.id } }); }); } remove(actor: Principal, id: string) { return this.access.mutate(actor, null, async (tx) => { const removed = await tx.address.deleteMany({ where: { id, userId: actor.userId, organizationId: actor.organizationId, }, }); if (!removed.count) throw new AppError('ADDRESS_NOT_FOUND'); await ensureDefault(tx, actor.userId); await recordAudit( tx, actor.organizationId, actor.userId, 'address.deleted', id, ); }); } }