- 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>
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
import { withAuth, type AuthContext } from '@/lib/api/helpers';
|
|
import { parseBody } from '@/lib/api/route-helpers';
|
|
import { db } from '@/lib/db';
|
|
import { userProfiles } from '@/lib/db/schema';
|
|
import { errorResponse } from '@/lib/errors';
|
|
import { z } from 'zod';
|
|
|
|
const updateProfileSchema = z.object({
|
|
displayName: z.string().min(1).max(200).optional(),
|
|
phone: z.string().nullable().optional(),
|
|
avatarUrl: z.string().url().nullable().optional(),
|
|
preferences: z
|
|
.object({
|
|
dark_mode: z.boolean().optional(),
|
|
locale: z.string().optional(),
|
|
timezone: z.string().optional(),
|
|
})
|
|
.passthrough()
|
|
.optional(),
|
|
});
|
|
|
|
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,
|
|
},
|
|
});
|
|
});
|
|
|
|
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),
|
|
});
|
|
if (!profile) {
|
|
return NextResponse.json({ error: 'Profile not found' }, { status: 404 });
|
|
}
|
|
|
|
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) {
|
|
updates.preferences = {
|
|
...((profile.preferences as Record<string, unknown>) ?? {}),
|
|
...body.preferences,
|
|
};
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|