67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import { cookies } from "next/headers";
|
|
import { z } from "zod";
|
|
import { apiV1Error, apiV1Success } from "@/server/api/v1/responses";
|
|
import { domainActorFromSession } from "@/server/auth/domain-actor";
|
|
import { getSessionContextFromHeaders } from "@/server/auth/session";
|
|
import { getServerConfig } from "@/server/config";
|
|
import { DomainError } from "@/server/domain/errors";
|
|
import {
|
|
getUserPreferences,
|
|
updateColorModePreference,
|
|
updateLanguagePreference,
|
|
} from "@/server/settings/preferences";
|
|
import {
|
|
COLOR_MODE_COOKIE,
|
|
COLOR_MODE_COOKIE_MAX_AGE,
|
|
isColorMode,
|
|
} from "@/lib/color-mode";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const inputSchema = z.object({
|
|
colorMode: z.string().optional(),
|
|
language: z.string().trim().min(2).max(12).optional(),
|
|
});
|
|
|
|
export async function PATCH(request: Request) {
|
|
try {
|
|
const context = await getSessionContextFromHeaders(new Headers(request.headers));
|
|
if (!context) {
|
|
throw new DomainError("UNAUTHENTICATED", "Authentication required.", {
|
|
messageKey: "api.errors.unauthenticated",
|
|
});
|
|
}
|
|
const parsed = inputSchema.safeParse(await request.json());
|
|
if (!parsed.success) {
|
|
throw new DomainError("VALIDATION_ERROR", "Invalid preference payload.", {
|
|
messageKey: "validation.required",
|
|
});
|
|
}
|
|
|
|
const actor = domainActorFromSession(context);
|
|
let preferences = getUserPreferences(actor);
|
|
if (parsed.data.language) {
|
|
preferences = updateLanguagePreference(actor, { language: parsed.data.language });
|
|
}
|
|
if (parsed.data.colorMode) {
|
|
if (!isColorMode(parsed.data.colorMode)) {
|
|
throw new DomainError("VALIDATION_ERROR", "Invalid color mode.");
|
|
}
|
|
preferences = updateColorModePreference(actor, { colorMode: parsed.data.colorMode });
|
|
const config = getServerConfig();
|
|
(await cookies()).set(COLOR_MODE_COOKIE, preferences.colorMode, {
|
|
httpOnly: false,
|
|
maxAge: COLOR_MODE_COOKIE_MAX_AGE,
|
|
path: "/",
|
|
sameSite: "lax",
|
|
secure: config.secureCookies,
|
|
});
|
|
}
|
|
|
|
return apiV1Success({ preferences });
|
|
} catch (error) {
|
|
return apiV1Error(error);
|
|
}
|
|
}
|