From e3811dc2809c834c210b8aee3f25e8249c915921 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Mon, 17 Aug 2026 15:55:18 +0200 Subject: [PATCH] =?UTF-8?q?Shared=20+=20migration:=20table=20user=5Fprefer?= =?UTF-8?q?ence=20(th=C3=A8me=20clair/sombre/syst=C3=A8me)=20(step=201/4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modèle Prisma UserPreference (1-1 avec UserProfile, onDelete Cascade), enum ThemePreference (LIGHT/DARK/SYSTEM, défaut SYSTEM) - packages/shared: THEME_PREFERENCES/ThemePreference, PreferencesView, updatePreferencesSchema --- .../migration.sql | 13 ++++++++++ apps/api/prisma/schema.prisma | 26 +++++++++++++++++++ packages/shared/src/index.ts | 2 ++ packages/shared/src/schemas/preferences.ts | 11 ++++++++ packages/shared/src/types/preferences.ts | 15 +++++++++++ 5 files changed, 67 insertions(+) create mode 100644 apps/api/prisma/migrations/20260817120000_add_user_preference/migration.sql create mode 100644 packages/shared/src/schemas/preferences.ts create mode 100644 packages/shared/src/types/preferences.ts diff --git a/apps/api/prisma/migrations/20260817120000_add_user_preference/migration.sql b/apps/api/prisma/migrations/20260817120000_add_user_preference/migration.sql new file mode 100644 index 0000000..f888721 --- /dev/null +++ b/apps/api/prisma/migrations/20260817120000_add_user_preference/migration.sql @@ -0,0 +1,13 @@ +-- CreateEnum +CREATE TYPE "ThemePreference" AS ENUM ('LIGHT', 'DARK', 'SYSTEM'); + +-- CreateTable +CREATE TABLE "user_preference" ( + "user_profile_id" INTEGER NOT NULL, + "theme" "ThemePreference" NOT NULL DEFAULT 'SYSTEM', + + CONSTRAINT "user_preference_pkey" PRIMARY KEY ("user_profile_id") +); + +-- AddForeignKey +ALTER TABLE "user_preference" ADD CONSTRAINT "user_preference_user_profile_id_fkey" FOREIGN KEY ("user_profile_id") REFERENCES "user_profiles"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 769943d..f398ab4 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -98,10 +98,36 @@ model UserProfile { /// a time — but Prisma models the admin side of a one-to-many FK as a /// list regardless of that real-world cardinality. administeredHouses House[] @relation("HouseAdmin") + preferences UserPreference? @@map("user_profiles") } +/// Not in the original spec doc — personalization settings (theme for now, +/// meant to grow), one row per profile, created on demand (see +/// `preferences.service.ts`) rather than at signup — same "absent means the +/// default" philosophy as `dietId`/allergies. +enum ThemePreference { + LIGHT + DARK + /// Follow the OS/browser preference — the default. Not "no row yet" (that + /// case is handled in the service layer) but an explicit choice to track + /// the system, distinguishable from a user who hasn't decided yet if this + /// model ever needs that distinction. + SYSTEM +} + +model UserPreference { + /// Both the primary key and the FK — a strict 1-1 with UserProfile, no + /// separate auto-incrementing id (a profile has at most one preferences row). + userProfileId Int @id @map("user_profile_id") + theme ThemePreference @default(SYSTEM) + + userProfile UserProfile @relation(fields: [userProfileId], references: [id], onDelete: Cascade) + + @@map("user_preference") +} + /// Explicit join table for the user_profiles <-> allergy association /// (documented in the spec as a plain many-to-many, no extra fields). model UserProfileAllergy { diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 3a7f5e4..d7a446f 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -8,9 +8,11 @@ export * from "./schemas/account.js"; export * from "./schemas/auth.js"; export * from "./schemas/household.js"; export * from "./schemas/planning.js"; +export * from "./schemas/preferences.js"; export * from "./schemas/profile.js"; export * from "./tools/assert-is-never.js"; export * from "./types/household.js"; export * from "./types/planning.js"; +export * from "./types/preferences.js"; export * from "./types/reference.js"; export * from "./types/user-profile.js"; diff --git a/packages/shared/src/schemas/preferences.ts b/packages/shared/src/schemas/preferences.ts new file mode 100644 index 0000000..c91e0e5 --- /dev/null +++ b/packages/shared/src/schemas/preferences.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; +import { THEME_PREFERENCES } from "../types/preferences.js"; + +// See schemas/auth.ts for the shared client/server validation rationale. + +/** Payload accepted by `PATCH /preferences`. */ +export const updatePreferencesSchema = z.object({ + theme: z.enum(THEME_PREFERENCES), +}); +/** Inferred TS type for {@link updatePreferencesSchema}'s validated output. */ +export type UpdatePreferencesInput = z.infer; diff --git a/packages/shared/src/types/preferences.ts b/packages/shared/src/types/preferences.ts new file mode 100644 index 0000000..dc3804b --- /dev/null +++ b/packages/shared/src/types/preferences.ts @@ -0,0 +1,15 @@ +/** + * The 3 values a profile's theme preference can take — `SYSTEM` means "no + * explicit choice, follow the OS/browser preference" (see `apps/web`'s + * `ThemeContext`, which maps this to *not* setting the `data-theme` + * attribute at all, letting `_theme.scss`'s `prefers-color-scheme` media + * query take over). + */ +export const THEME_PREFERENCES = ["LIGHT", "DARK", "SYSTEM"] as const; +/** Inferred TS type for one {@link THEME_PREFERENCES} member. */ +export type ThemePreference = (typeof THEME_PREFERENCES)[number]; + +/** A profile's personalization preferences, as returned by `GET /preferences` / `PATCH /preferences`. */ +export interface PreferencesView { + theme: ThemePreference; +}