Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import { NextResponse } from 'next/server';
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import { withAuth, type AuthContext } from '@/lib/api/helpers';
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
import { parseBody } from '@/lib/api/route-helpers';
|
|
|
|
|
import { db } from '@/lib/db';
|
|
|
|
|
import { userProfiles } from '@/lib/db/schema';
|
fix(audit-tier-6): validation, perms, ops/infra, per-port webhook secret
Final audit polish — closes the remaining LOW + MED items the previous
tiers didn't reach:
* Validation hardening: me.preferences uses .strict() + 8KB cap
instead of unbounded .passthrough(); files.uploadFile gains
magic-byte verification (jpeg/png/gif/webp/pdf/doc/xlsx); OCR scan
endpoint enforces 10MB cap + magic-byte check on receipt images;
port logoUrl + me.avatarUrl reject javascript:/data: schemes via
a shared httpUrl refinement.
* Permission gates: document-sends/{brochure,berth-pdf} now require
email.send (was withAuth-only); document-sends/{preview,list} on
email.view; ai/email-draft on email.send; documents/[id]/send
uses send_for_signing (was create); expenses/export/parent-company
flips from hard isSuperAdmin to expenses.export for parity;
admin/users/options gated on reminders.assign_others (was withAuth).
* Envelope hygiene: auth/set-password switches the third {message}
variant to errorResponse + {data: {email}}; ai/email-draft wraps
jobId in {data: {jobId}}.
* UI polish: reports-list.handleDownload surfaces failures via
toastError (was console-only).
* Ops/infra: pin pnpm@10.33.2 across all three Dockerfiles +
packageManager field in package.json; Dockerfile.worker re-orders
user creation BEFORE pnpm install so node_modules / .cache dirs
are worker-owned (fixes tesseract.js + sharp EACCES at first PDF
parse); add Redis-ping HEALTHCHECK to the worker container.
* Public health endpoint: returns full env+appUrl payload only when
the caller presents X-Intake-Secret, otherwise a minimal {status}
so generic uptime monitors still work but anonymous internet
doesn't get deployment fingerprints.
* Per-port Documenso webhook secret: new system_settings key
+ listDocumensoWebhookSecrets() helper. The webhook receiver
iterates every configured per-port secret with timing-safe
comparison + falls back to env, then forwards the resolved portId
into handleDocumentExpired so two ports sharing a documensoId
cannot cross-mutate.
Deferred (handled in dedicated follow-up PRs):
* Tier 5.1 — direct service tests for portal-auth / users /
email-accounts / document-sends / sales-email-config. MED, large
test-writing scope.
* The {ok: true} → {data: null} envelope migration across
alerts/expenses/admin-ocr-settings/storage routes. Mechanical but
needs coordinated client + test updates.
* CSP-nonce migration (drop unsafe-inline) — needs middleware-level
nonce generation that the Next 15 router has to thread through.
* Idempotency-Key header on Documenso createDocument. Requires
schema column on documents to persist the key; deferred so it
doesn't bundle a migration into this commit.
* The 16 better-auth user_id FKs — separate dedicated migration
with care (some columns are NOT NULL today and cascade decisions
matter).
* PermissionGate / Skeleton / EmptyState wraps across 5 admin lists
(auditor-H §§36–37) and the residential-clients filter bar.
Test status: 1175/1175 vitest, tsc clean.
Refs: docs/audit-comprehensive-2026-05-05.md MED §§28,29,30 + LOW §§32–43
+ HIGH §9 (Documenso secrets follow-up).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:03:31 +02:00
|
|
|
import { errorResponse, NotFoundError, ValidationError } from '@/lib/errors';
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
import { z } from 'zod';
|
|
|
|
|
|
|
|
|
|
const updateProfileSchema = z.object({
|
|
|
|
|
displayName: z.string().min(1).max(200).optional(),
|
|
|
|
|
phone: z.string().nullable().optional(),
|
fix(audit-tier-6): validation, perms, ops/infra, per-port webhook secret
Final audit polish — closes the remaining LOW + MED items the previous
tiers didn't reach:
* Validation hardening: me.preferences uses .strict() + 8KB cap
instead of unbounded .passthrough(); files.uploadFile gains
magic-byte verification (jpeg/png/gif/webp/pdf/doc/xlsx); OCR scan
endpoint enforces 10MB cap + magic-byte check on receipt images;
port logoUrl + me.avatarUrl reject javascript:/data: schemes via
a shared httpUrl refinement.
* Permission gates: document-sends/{brochure,berth-pdf} now require
email.send (was withAuth-only); document-sends/{preview,list} on
email.view; ai/email-draft on email.send; documents/[id]/send
uses send_for_signing (was create); expenses/export/parent-company
flips from hard isSuperAdmin to expenses.export for parity;
admin/users/options gated on reminders.assign_others (was withAuth).
* Envelope hygiene: auth/set-password switches the third {message}
variant to errorResponse + {data: {email}}; ai/email-draft wraps
jobId in {data: {jobId}}.
* UI polish: reports-list.handleDownload surfaces failures via
toastError (was console-only).
* Ops/infra: pin pnpm@10.33.2 across all three Dockerfiles +
packageManager field in package.json; Dockerfile.worker re-orders
user creation BEFORE pnpm install so node_modules / .cache dirs
are worker-owned (fixes tesseract.js + sharp EACCES at first PDF
parse); add Redis-ping HEALTHCHECK to the worker container.
* Public health endpoint: returns full env+appUrl payload only when
the caller presents X-Intake-Secret, otherwise a minimal {status}
so generic uptime monitors still work but anonymous internet
doesn't get deployment fingerprints.
* Per-port Documenso webhook secret: new system_settings key
+ listDocumensoWebhookSecrets() helper. The webhook receiver
iterates every configured per-port secret with timing-safe
comparison + falls back to env, then forwards the resolved portId
into handleDocumentExpired so two ports sharing a documensoId
cannot cross-mutate.
Deferred (handled in dedicated follow-up PRs):
* Tier 5.1 — direct service tests for portal-auth / users /
email-accounts / document-sends / sales-email-config. MED, large
test-writing scope.
* The {ok: true} → {data: null} envelope migration across
alerts/expenses/admin-ocr-settings/storage routes. Mechanical but
needs coordinated client + test updates.
* CSP-nonce migration (drop unsafe-inline) — needs middleware-level
nonce generation that the Next 15 router has to thread through.
* Idempotency-Key header on Documenso createDocument. Requires
schema column on documents to persist the key; deferred so it
doesn't bundle a migration into this commit.
* The 16 better-auth user_id FKs — separate dedicated migration
with care (some columns are NOT NULL today and cascade decisions
matter).
* PermissionGate / Skeleton / EmptyState wraps across 5 admin lists
(auditor-H §§36–37) and the residential-clients filter bar.
Test status: 1175/1175 vitest, tsc clean.
Refs: docs/audit-comprehensive-2026-05-05.md MED §§28,29,30 + LOW §§32–43
+ HIGH §9 (Documenso secrets follow-up).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:03:31 +02:00
|
|
|
// Refuse `javascript:` / `data:` schemes — z.string().url() lets them
|
|
|
|
|
// through and `<a href={avatarUrl}>` would otherwise be a stored-XSS
|
|
|
|
|
// vector if any future renderer treated the value as a link.
|
|
|
|
|
avatarUrl: z
|
|
|
|
|
.string()
|
|
|
|
|
.url()
|
|
|
|
|
.refine((u) => /^https?:\/\//i.test(u), 'must be an http(s) URL')
|
|
|
|
|
.nullable()
|
|
|
|
|
.optional(),
|
|
|
|
|
// Strict allow-list — no `.passthrough()` here. The previous schema let
|
|
|
|
|
// arbitrary client-supplied keys survive validation and persist into
|
|
|
|
|
// `userProfiles.preferences` JSONB unbounded; auditor-E3 §28 caught this.
|
|
|
|
|
// Add new keys here as the UI surfaces them rather than letting the
|
|
|
|
|
// client mint them at will.
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
preferences: z
|
|
|
|
|
.object({
|
|
|
|
|
dark_mode: z.boolean().optional(),
|
|
|
|
|
locale: z.string().optional(),
|
|
|
|
|
timezone: z.string().optional(),
|
|
|
|
|
})
|
fix(audit-tier-6): validation, perms, ops/infra, per-port webhook secret
Final audit polish — closes the remaining LOW + MED items the previous
tiers didn't reach:
* Validation hardening: me.preferences uses .strict() + 8KB cap
instead of unbounded .passthrough(); files.uploadFile gains
magic-byte verification (jpeg/png/gif/webp/pdf/doc/xlsx); OCR scan
endpoint enforces 10MB cap + magic-byte check on receipt images;
port logoUrl + me.avatarUrl reject javascript:/data: schemes via
a shared httpUrl refinement.
* Permission gates: document-sends/{brochure,berth-pdf} now require
email.send (was withAuth-only); document-sends/{preview,list} on
email.view; ai/email-draft on email.send; documents/[id]/send
uses send_for_signing (was create); expenses/export/parent-company
flips from hard isSuperAdmin to expenses.export for parity;
admin/users/options gated on reminders.assign_others (was withAuth).
* Envelope hygiene: auth/set-password switches the third {message}
variant to errorResponse + {data: {email}}; ai/email-draft wraps
jobId in {data: {jobId}}.
* UI polish: reports-list.handleDownload surfaces failures via
toastError (was console-only).
* Ops/infra: pin pnpm@10.33.2 across all three Dockerfiles +
packageManager field in package.json; Dockerfile.worker re-orders
user creation BEFORE pnpm install so node_modules / .cache dirs
are worker-owned (fixes tesseract.js + sharp EACCES at first PDF
parse); add Redis-ping HEALTHCHECK to the worker container.
* Public health endpoint: returns full env+appUrl payload only when
the caller presents X-Intake-Secret, otherwise a minimal {status}
so generic uptime monitors still work but anonymous internet
doesn't get deployment fingerprints.
* Per-port Documenso webhook secret: new system_settings key
+ listDocumensoWebhookSecrets() helper. The webhook receiver
iterates every configured per-port secret with timing-safe
comparison + falls back to env, then forwards the resolved portId
into handleDocumentExpired so two ports sharing a documensoId
cannot cross-mutate.
Deferred (handled in dedicated follow-up PRs):
* Tier 5.1 — direct service tests for portal-auth / users /
email-accounts / document-sends / sales-email-config. MED, large
test-writing scope.
* The {ok: true} → {data: null} envelope migration across
alerts/expenses/admin-ocr-settings/storage routes. Mechanical but
needs coordinated client + test updates.
* CSP-nonce migration (drop unsafe-inline) — needs middleware-level
nonce generation that the Next 15 router has to thread through.
* Idempotency-Key header on Documenso createDocument. Requires
schema column on documents to persist the key; deferred so it
doesn't bundle a migration into this commit.
* The 16 better-auth user_id FKs — separate dedicated migration
with care (some columns are NOT NULL today and cascade decisions
matter).
* PermissionGate / Skeleton / EmptyState wraps across 5 admin lists
(auditor-H §§36–37) and the residential-clients filter bar.
Test status: 1175/1175 vitest, tsc clean.
Refs: docs/audit-comprehensive-2026-05-05.md MED §§28,29,30 + LOW §§32–43
+ HIGH §9 (Documenso secrets follow-up).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:03:31 +02:00
|
|
|
.strict()
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
.optional(),
|
|
|
|
|
});
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
export const GET = withAuth(async (_req, ctx: AuthContext) => {
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
data: {
|
|
|
|
|
userId: ctx.userId,
|
|
|
|
|
portId: ctx.portId,
|
|
|
|
|
portSlug: ctx.portSlug,
|
|
|
|
|
permissions: ctx.permissions,
|
|
|
|
|
isSuperAdmin: ctx.isSuperAdmin,
|
|
|
|
|
user: ctx.user,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
|
|
|
|
|
export const PATCH = withAuth(async (req, ctx: AuthContext) => {
|
|
|
|
|
try {
|
|
|
|
|
const body = await parseBody(req, updateProfileSchema);
|
|
|
|
|
|
|
|
|
|
const profile = await db.query.userProfiles.findFirst({
|
|
|
|
|
where: eq(userProfiles.userId, ctx.userId),
|
|
|
|
|
});
|
fix(audit-tier-2-routes): manual NextResponse.json error sweep + admin form banners
Two final waves of error-surface hygiene closing the audit's MED §12 +
HIGH §15 + HIGH §17 findings:
* 50 route files swept (61 sites): manual NextResponse.json({error,
status: 4xx|5xx}) early-returns replaced by typed throws +
errorResponse(err) at the catch.
- Super-admin gates (13 sites) use new requireSuperAdmin(ctx, action)
helper from src/lib/api/helpers.ts so denials hit the audit log.
- Path-param + body validation 400s become ValidationError throws.
- 404s become NotFoundError or CodedError('NOT_FOUND') for AI
feature-flag paths.
- 11 manual 5xx returns now re-throw so error_events captures the
request-id (the admin error inspector becomes usable from real
incidents).
- website-analytics 200-with-error anti-pattern flipped to 409 +
UMAMI_NOT_CONFIGURED. 502 upstream paths use UMAMI_UPSTREAM_ERROR.
- 11 sites intentionally preserved: storage/[token] anti-enumeration
token-failure paths, webhook-secret 401, "Unknown port" 400 in
public intake.
* 7 admin forms (roles, users, ports, webhooks, custom-fields,
document-templates, tags) gain a formatErrorBanner() helper from
src/lib/api/toast-error.ts that builds a multi-line "Error code / Reference ID"
banner — the rep can copy the request id when reporting a failed
save. Banners get whitespace-pre-line so newlines render.
Test status: 1168/1168 vitest, tsc clean.
Refs: docs/audit-comprehensive-2026-05-05.md MED §12 (auditor-F Issue 1)
+ HIGH §15 (auditor-F Issue 2) + HIGH §17 (auditor-H Issue 2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 20:36:59 +02:00
|
|
|
if (!profile) throw new NotFoundError('profile');
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
|
|
|
|
|
const updates: Record<string, unknown> = { updatedAt: new Date() };
|
|
|
|
|
if (body.displayName !== undefined) updates.displayName = body.displayName;
|
|
|
|
|
if (body.phone !== undefined) updates.phone = body.phone;
|
|
|
|
|
if (body.avatarUrl !== undefined) updates.avatarUrl = body.avatarUrl;
|
|
|
|
|
if (body.preferences !== undefined) {
|
2026-05-05 21:19:39 +02:00
|
|
|
// Allow-list — only retain keys defined in the strict schema. Pre-
|
|
|
|
|
// strict rows may carry extra keys from when the schema was
|
|
|
|
|
// .passthrough(); the merge prunes them so legacy bloat doesn't
|
|
|
|
|
// accumulate forever, and a future schema regression that tries
|
|
|
|
|
// to ship arbitrary keys still gets dropped here at write time.
|
|
|
|
|
const ALLOWED_PREF_KEYS = new Set(['dark_mode', 'locale', 'timezone']);
|
|
|
|
|
const existing = (profile.preferences as Record<string, unknown>) ?? {};
|
|
|
|
|
const merged = Object.fromEntries(
|
|
|
|
|
Object.entries({ ...existing, ...body.preferences }).filter(([k]) =>
|
|
|
|
|
ALLOWED_PREF_KEYS.has(k),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
// Hard cap on the merged JSONB — defense in depth against any
|
|
|
|
|
// future schema growth that might re-introduce free-form keys.
|
fix(audit-tier-6): validation, perms, ops/infra, per-port webhook secret
Final audit polish — closes the remaining LOW + MED items the previous
tiers didn't reach:
* Validation hardening: me.preferences uses .strict() + 8KB cap
instead of unbounded .passthrough(); files.uploadFile gains
magic-byte verification (jpeg/png/gif/webp/pdf/doc/xlsx); OCR scan
endpoint enforces 10MB cap + magic-byte check on receipt images;
port logoUrl + me.avatarUrl reject javascript:/data: schemes via
a shared httpUrl refinement.
* Permission gates: document-sends/{brochure,berth-pdf} now require
email.send (was withAuth-only); document-sends/{preview,list} on
email.view; ai/email-draft on email.send; documents/[id]/send
uses send_for_signing (was create); expenses/export/parent-company
flips from hard isSuperAdmin to expenses.export for parity;
admin/users/options gated on reminders.assign_others (was withAuth).
* Envelope hygiene: auth/set-password switches the third {message}
variant to errorResponse + {data: {email}}; ai/email-draft wraps
jobId in {data: {jobId}}.
* UI polish: reports-list.handleDownload surfaces failures via
toastError (was console-only).
* Ops/infra: pin pnpm@10.33.2 across all three Dockerfiles +
packageManager field in package.json; Dockerfile.worker re-orders
user creation BEFORE pnpm install so node_modules / .cache dirs
are worker-owned (fixes tesseract.js + sharp EACCES at first PDF
parse); add Redis-ping HEALTHCHECK to the worker container.
* Public health endpoint: returns full env+appUrl payload only when
the caller presents X-Intake-Secret, otherwise a minimal {status}
so generic uptime monitors still work but anonymous internet
doesn't get deployment fingerprints.
* Per-port Documenso webhook secret: new system_settings key
+ listDocumensoWebhookSecrets() helper. The webhook receiver
iterates every configured per-port secret with timing-safe
comparison + falls back to env, then forwards the resolved portId
into handleDocumentExpired so two ports sharing a documensoId
cannot cross-mutate.
Deferred (handled in dedicated follow-up PRs):
* Tier 5.1 — direct service tests for portal-auth / users /
email-accounts / document-sends / sales-email-config. MED, large
test-writing scope.
* The {ok: true} → {data: null} envelope migration across
alerts/expenses/admin-ocr-settings/storage routes. Mechanical but
needs coordinated client + test updates.
* CSP-nonce migration (drop unsafe-inline) — needs middleware-level
nonce generation that the Next 15 router has to thread through.
* Idempotency-Key header on Documenso createDocument. Requires
schema column on documents to persist the key; deferred so it
doesn't bundle a migration into this commit.
* The 16 better-auth user_id FKs — separate dedicated migration
with care (some columns are NOT NULL today and cascade decisions
matter).
* PermissionGate / Skeleton / EmptyState wraps across 5 admin lists
(auditor-H §§36–37) and the residential-clients filter bar.
Test status: 1175/1175 vitest, tsc clean.
Refs: docs/audit-comprehensive-2026-05-05.md MED §§28,29,30 + LOW §§32–43
+ HIGH §9 (Documenso secrets follow-up).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:03:31 +02:00
|
|
|
const serialized = JSON.stringify(merged);
|
|
|
|
|
if (Buffer.byteLength(serialized, 'utf8') > 8 * 1024) {
|
|
|
|
|
throw new ValidationError('preferences exceeds 8KB');
|
|
|
|
|
}
|
|
|
|
|
updates.preferences = merged;
|
Add user settings, audit log, berth CRUD, and missing endpoints
- PATCH /api/v1/me: self-service profile update (name, phone, timezone)
- User settings page with profile editor + notification preferences
- Audit log API with filtering (entity, action, user, date range)
- Audit log page with search, entity type, and action filters
- Berth create/delete: POST /api/v1/berths + DELETE /api/v1/berths/[id]
- Client duplicates endpoint: GET /api/v1/clients/duplicates?name=
- Replace settings and audit stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 19:45:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [updated] = await db
|
|
|
|
|
.update(userProfiles)
|
|
|
|
|
.set(updates)
|
|
|
|
|
.where(eq(userProfiles.userId, ctx.userId))
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
data: {
|
|
|
|
|
userId: updated!.userId,
|
|
|
|
|
displayName: updated!.displayName,
|
|
|
|
|
phone: updated!.phone,
|
|
|
|
|
avatarUrl: updated!.avatarUrl,
|
|
|
|
|
preferences: updated!.preferences,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return errorResponse(error);
|
|
|
|
|
}
|
|
|
|
|
});
|