refactor(services): centralize AuditMeta + transactional setEntityTags helper
The same `interface AuditMeta { userId; portId; ipAddress; userAgent }`
was duplicated in 26 service files. Move the canonical definition into
`@/lib/audit` next to the related types and update every service to
import it. `ServiceAuditMeta` (the alias used in invoices.ts and
expenses.ts) collapses into the same name.
Tag CRUD across clients/companies/yachts/interests/berths followed an
identical wipe-then-rewrite recipe with two latent issues: the delete
and insert weren't wrapped in a transaction (a partial failure left
the entity with zero tags) and the audit-log payload shape diverged
(`newValue: { tagIds }` for clients/yachts/companies but
`metadata: { type: 'tags_updated', tagIds }` for interests/berths).
Extract `setEntityTags` in `entity-tags.helper.ts` that performs the
delete+insert inside a single transaction, normalizes the audit payload
to `newValue: { tagIds }`, and dispatches the per-entity socket event
through a switch so `ServerToClientEvents` typing stays intact.
The five `setXTags(...)` service functions now do parent-row tenant
verification and delegate the join-table work + side effects.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import type { PgColumn } from 'drizzle-orm/pg-core';
|
||||
import { db } from '@/lib/db';
|
||||
import { expenses, invoices, invoiceExpenses } from '@/lib/db/schema/financial';
|
||||
import { buildListQuery } from '@/lib/db/query-builder';
|
||||
import { createAuditLog } from '@/lib/audit';
|
||||
import { createAuditLog, type AuditMeta } from '@/lib/audit';
|
||||
import { diffEntity } from '@/lib/entity-diff';
|
||||
import { softDelete, restore } from '@/lib/db/utils';
|
||||
import { NotFoundError, ConflictError } from '@/lib/errors';
|
||||
@@ -19,14 +19,6 @@ import type {
|
||||
|
||||
export type { ListExpensesInput };
|
||||
|
||||
// AuditMeta type expected by service functions
|
||||
export interface ServiceAuditMeta {
|
||||
userId: string;
|
||||
portId: string;
|
||||
ipAddress: string;
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
export async function listExpenses(portId: string, query: ListExpensesInput) {
|
||||
const filters = [];
|
||||
|
||||
@@ -79,11 +71,7 @@ export async function getExpenseById(id: string, portId: string) {
|
||||
return expense;
|
||||
}
|
||||
|
||||
export async function createExpense(
|
||||
portId: string,
|
||||
data: CreateExpenseInput,
|
||||
meta: ServiceAuditMeta,
|
||||
) {
|
||||
export async function createExpense(portId: string, data: CreateExpenseInput, meta: AuditMeta) {
|
||||
let amountUsd: string | null = null;
|
||||
let exchangeRate: string | null = null;
|
||||
|
||||
@@ -163,7 +151,7 @@ export async function updateExpense(
|
||||
id: string,
|
||||
portId: string,
|
||||
data: UpdateExpenseInput,
|
||||
meta: ServiceAuditMeta,
|
||||
meta: AuditMeta,
|
||||
) {
|
||||
const existing = await getExpenseById(id, portId);
|
||||
|
||||
@@ -226,7 +214,7 @@ export async function updateExpense(
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function archiveExpense(id: string, portId: string, meta: ServiceAuditMeta) {
|
||||
export async function archiveExpense(id: string, portId: string, meta: AuditMeta) {
|
||||
const existing = await getExpenseById(id, portId);
|
||||
|
||||
// BR-045: Check if linked to non-draft invoice
|
||||
@@ -257,7 +245,7 @@ export async function archiveExpense(id: string, portId: string, meta: ServiceAu
|
||||
emitToRoom(`port:${portId}`, 'expense:archived', { expenseId: id });
|
||||
}
|
||||
|
||||
export async function restoreExpense(id: string, portId: string, meta: ServiceAuditMeta) {
|
||||
export async function restoreExpense(id: string, portId: string, meta: AuditMeta) {
|
||||
await getExpenseById(id, portId);
|
||||
|
||||
await restore(expenses, expenses.id, id);
|
||||
@@ -287,7 +275,7 @@ export async function addReceiptFiles(
|
||||
id: string,
|
||||
portId: string,
|
||||
fileIds: string[],
|
||||
meta: ServiceAuditMeta,
|
||||
meta: AuditMeta,
|
||||
) {
|
||||
await getExpenseById(id, portId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user