From ba827891666418db04c3690b2f5474f39244cb22 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 1 Aug 2026 15:02:21 +0200 Subject: [PATCH] chore(db): finish Prisma cutover to Drizzle Kit tooling Move CMS SQL to drizzle/migrations, drop prisma packages/schema, wire drizzle-kit scripts, and regenerate schema names from src/db/schema.ts. Co-authored-by: Cursor --- .gitea/workflows/ci.yaml | 6 +- .gitignore | 1 - README.md | 48 +- drizzle.config.ts | 11 +- drizzle/drafts/.gitkeep | 0 drizzle/drafts/README.md | 1 + .../0001_create_website_settings.sql | 0 .../migrations/0002_users_2fa.sql | 0 .../migrations/0003_password_resets.sql | 0 .../migrations/0004_cms_feature_tables.sql | 0 .../0005_radio_listener_points_columns.sql | 0 .../migrations/0006_writeable_boxes.sql | 0 .../migrations/0007_login_logs.sql | 0 .../0008_add_bans_user_id_index.sql | 0 .../0009_radio_contests_giveaways_columns.sql | 0 .../migrations/0010_admin_feature_tables.sql | 0 .../migrations/0011_dynamic_rank_acl.sql | 0 .../migrations/0012_seed_acl_permissions.sql | 0 ...13_admin_events_polls_banners_prefixes.sql | 0 ...14_complete_acl_and_import_permissions.sql | 0 .../migrations/0015_radio_acl_permissions.sql | 0 .../0016_housekeeping_group_field.sql | 0 .../migrations/0017_website_jwt_version.sql | 0 .../0018_acl_repair_admin_nav_grants.sql | 0 .../0019_add_website_login_logs.sql | 0 package.json | 6 +- pnpm-lock.yaml | 210 +- pnpm-workspace.yaml | 3 - prisma.config.ts | 16 - prisma/schema.prisma | 2457 ----------------- scripts/apply-migrations.ts | 2 +- scripts/generate-drizzle-schema.mjs | 335 +-- scripts/migration-idempotency.test.ts | 2 +- src/app/(site)/leaderboard/page.tsx | 2 +- src/app/(site)/photos/page.tsx | 2 +- src/components/public/photo-lightbox.tsx | 2 +- src/components/public/radio-player.tsx | 2 +- src/db/schema.ts | 4 +- .../admin-content-modules-contract.test.ts | 8 +- src/lib/admin/acl-management-contract.test.ts | 2 +- src/lib/admin/acl-migration-contract.test.ts | 2 +- src/lib/ci-workflow-contract.test.ts | 1 + src/lib/foundation/action.ts | 8 - src/lib/permission-slugs.ts | 2 +- src/lib/proxy-auth-contract.test.ts | 2 +- src/lib/services/upload-import.ts | 2 +- src/lib/staff-smoke-contract.test.ts | 15 + 47 files changed, 365 insertions(+), 2787 deletions(-) create mode 100644 drizzle/drafts/.gitkeep create mode 100644 drizzle/drafts/README.md rename {prisma => drizzle}/migrations/0001_create_website_settings.sql (100%) rename {prisma => drizzle}/migrations/0002_users_2fa.sql (100%) rename {prisma => drizzle}/migrations/0003_password_resets.sql (100%) rename {prisma => drizzle}/migrations/0004_cms_feature_tables.sql (100%) rename {prisma => drizzle}/migrations/0005_radio_listener_points_columns.sql (100%) rename {prisma => drizzle}/migrations/0006_writeable_boxes.sql (100%) rename {prisma => drizzle}/migrations/0007_login_logs.sql (100%) rename {prisma => drizzle}/migrations/0008_add_bans_user_id_index.sql (100%) rename {prisma => drizzle}/migrations/0009_radio_contests_giveaways_columns.sql (100%) rename {prisma => drizzle}/migrations/0010_admin_feature_tables.sql (100%) rename {prisma => drizzle}/migrations/0011_dynamic_rank_acl.sql (100%) rename {prisma => drizzle}/migrations/0012_seed_acl_permissions.sql (100%) rename {prisma => drizzle}/migrations/0013_admin_events_polls_banners_prefixes.sql (100%) rename {prisma => drizzle}/migrations/0014_complete_acl_and_import_permissions.sql (100%) rename {prisma => drizzle}/migrations/0015_radio_acl_permissions.sql (100%) rename {prisma => drizzle}/migrations/0016_housekeeping_group_field.sql (100%) rename {prisma => drizzle}/migrations/0017_website_jwt_version.sql (100%) rename {prisma => drizzle}/migrations/0018_acl_repair_admin_nav_grants.sql (100%) rename {prisma => drizzle}/migrations/0019_add_website_login_logs.sql (100%) delete mode 100644 prisma.config.ts delete mode 100644 prisma/schema.prisma diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 000ee30c..a89a7962 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -43,7 +43,7 @@ jobs: export REDIS_URL="redis://127.0.0.1:6379?connect_timeout=1" export BCRYPT_ROUNDS=4 pnpm install --frozen-lockfile - # Types come from the committed Drizzle schema (src/db/schema.ts); no prisma:generate. + # Types come from the committed Drizzle schema (src/db/schema.ts). pnpm biome:lint pnpm typecheck pnpm test @@ -139,7 +139,7 @@ jobs: echo "STAGE DATABASE_POOL_SIZE=${DATABASE_POOL_SIZE}" pnpm install --frozen-lockfile - # Types come from the committed Drizzle schema (src/db/schema.ts); no prisma:generate. + # Types come from the committed Drizzle schema (src/db/schema.ts). export BCRYPT_ROUNDS=4 pnpm typecheck # Validate production env (AUTH_SECRET, DATABASE_URL, …) during build. @@ -292,7 +292,7 @@ jobs: export NODE_ENV=production export SKIP_ENV_VALIDATION=1 pnpm install --frozen-lockfile - # Types come from the committed Drizzle schema (src/db/schema.ts); no prisma:generate. + # Types come from the committed Drizzle schema (src/db/schema.ts). # Tag releases must apply CMS SQL migrations against the live DB # (same path as push-to-main deploy), using the production .env. LIVE="/var/www/atom-nexst" diff --git a/.gitignore b/.gitignore index 3691cfca..1f9e9026 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ node_modules/ next-env.d.ts .env *.tsbuildinfo -# Prisma client is generated by `prisma generate` src/generated/ # Runtime avatar/badge imaging disk cache public/cache/ diff --git a/README.md b/README.md index 195f1df1..0b584f56 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ CREATE DATABASE IF NOT EXISTS epicnext_cms CHARACTER SET utf8mb4 COLLATE utf8mb4 The CMS reads emulator-owned tables (`users`, `items`, `rooms`, `bans`, etc.) directly. It never creates, alters, or drops them. The Drizzle schema in `src/db/schema.ts` is generated from the existing database structure and does not modify it. -> **Note:** The CMS does **not** own the database schema — it maps to tables that are managed by the emulator. All Drizzle schema definitions use `drizzle-orm`'s runtime mapping (no `drizzle-kit push/migrate` is ever run against the emulator schema). CMS-owned tables (`website_*`, `radio_*`, etc.) are created via idempotent SQL files in `prisma/migrations/`. +> **Note:** The CMS does **not** own the emulator schema — it maps to those tables via Drizzle. Never run `drizzle-kit push` / `migrate` against the shared DB. CMS-owned tables (`website_*`, `radio_*`, etc.) are created via idempotent SQL in `drizzle/migrations/` (`pnpm db:migrate`). ### 3. Configure Environment @@ -78,23 +78,22 @@ const found = await db.select() | Command | What it does | | ------- | ------------ | -| `npx drizzle-kit generate --dialect mysql --schema src/db/schema.ts --out src/db/migrations` | Inspect the Drizzle schema and emit migration SQL | -| `npx drizzle-kit studio` | Open a local DB browser (dev only) | -| `npx drizzle-kit introspect` | Reverse-engineer an existing DB into a Drizzle schema | +| `pnpm db:generate` | Draft SQL from Drizzle schema into `drizzle/drafts/` (review + copy into `drizzle/migrations/`) | +| `pnpm db:studio` | Open Drizzle Studio (dev only) | +| `pnpm db:introspect` | Reverse-engineer an existing DB into a Drizzle schema draft | +| `pnpm db:schema:generate` | Regen committed `src/db/schema.ts` from previous schema names + live DB | -> The CMS does **not** use `drizzle-kit push` — the database is owned by the emulator and is never auto-migrated. CMS-owned tables are created via the SQL migration runner (step 5). +> The CMS does **not** use `drizzle-kit push` or `drizzle-kit migrate` — the database is shared with the emulator. Apply CMS DDL only via `pnpm db:migrate`. Use `import { db } from "@/lib/db"` with table definitions from `src/db/schema.ts` for all database access. Types come from the committed Drizzle schema — no separate client code generation is required at build time. -`prisma/schema.prisma` remains as a reference model list used by `pnpm db:schema:generate` when regenerating the Drizzle schema from the live database. The Prisma client is not used at runtime. - ### 5. Run CMS Migrations ```bash pnpm db:migrate ``` -Creates all CMS-owned tables (`website_*`, `radio_*`, `acl_*`, `admin_audit_log`, etc.) via idempotent SQL files in `prisma/migrations/`. Emulator tables are never touched. +Creates all CMS-owned tables (`website_*`, `radio_*`, `acl_*`, `admin_audit_log`, etc.) via idempotent SQL files in `drizzle/migrations/`. Emulator tables are never touched. Check migration status: @@ -154,17 +153,15 @@ The CMS runs behind an nginx reverse proxy on the default port 3000. Static asse | `pnpm test` | Run all tests (Vitest) | | `pnpm db:migrate` | Apply pending SQL migrations | | `pnpm db:migrate:status` | Show migration status | -| `pnpm db:schema:generate` | Regen `src/db/schema.ts` from Prisma + live DB (needs `DATABASE_URL`) | +| `pnpm db:schema:generate` | Regen `src/db/schema.ts` from prior schema + live DB | +| `pnpm db:generate` | Draft SQL via drizzle-kit → `drizzle/drafts/` | +| `pnpm db:studio` | Drizzle Studio (dev) | +| `pnpm db:introspect` | drizzle-kit introspect (draft) | | `pnpm analyze` | Build + open bundle analyzer | | `pnpm jobs:worker` | Start background task worker (systemd / PM2) | | `pnpm biome:check` | Lint and format code | -**Drizzle CLI (dev only, run with `npx`):** -| Command | Description | -| ------- | ----------- | -| `drizzle-kit generate` | Generate migration SQL from Drizzle schema | -| `drizzle-kit studio` | Local Drizzle Studio database browser | -| `drizzle-kit introspect` | Reverse-engineer DB → Drizzle schema | +**Drizzle Kit notes:** `db:generate` / `db:introspect` write drafts only. Reviewed SQL must be copied into `drizzle/migrations/` as a new numbered file, then applied with `pnpm db:migrate`. Never run `drizzle-kit push` or `drizzle-kit migrate` against production. --- @@ -192,18 +189,18 @@ The CMS runs behind an nginx reverse proxy on the default port 3000. Static asse ## Architecture ``` -├── prisma/ -│ ├── schema.prisma # Reference model list — input for db:schema:generate -│ └── migrations/ # 19 SQL migrations for CMS tables (idempotent, never re-run) +├── drizzle/ +│ ├── migrations/ # CMS SQL migrations (idempotent, tracked in cms_migrations) +│ └── drafts/ # drizzle-kit generate output (never auto-applied) ├── scripts/ │ ├── apply-migrations.ts # SQL migration runner (apply + status) │ ├── jobs-worker.ts # Background task scheduler │ ├── merge-config.cjs # Utility: merge split config files -│ └── generate-drizzle-schema.mjs # Generate src/db/schema.ts from schema.prisma + live DB +│ └── generate-drizzle-schema.mjs # Regen src/db/schema.ts from schema + live DB ├── src/ │ ├── db/ -│ │ ├── schema.ts # Drizzle ORM schema (176 tables — runtime data layer) -│ │ └── migrations/ # Drizzle migration files (local dev only) +│ │ ├── schema.ts # Drizzle ORM schema (committed — runtime data layer) +│ │ └── relations.ts # Drizzle relations │ ├── app/ # Next.js App Router (pages & API routes) │ ├── actions/ # Server Actions │ ├── components/ # UI components @@ -232,13 +229,14 @@ The CMS runs behind an nginx reverse proxy on the default port 3000. Static asse | Component | Type | Migrations | | ------------------------------------------------- | ----------------------- | ----------------------------------- | | Emulator tables (`users`, `items`, `rooms`, etc.) | Existing Polaris schema | None — CMS reads/writes only | -| CMS tables (`website_*`, `radio_*`, etc.) | CMS-owned | `prisma/migrations/*.sql` (19 files) | +| CMS tables (`website_*`, `radio_*`, etc.) | CMS-owned | `drizzle/migrations/*.sql` | | Migration tracking | `cms_migrations` table | Auto-created by migration runner | ### ORM Architecture -- **Runtime (Drizzle ORM)**: `@/lib/db` exposes a Drizzle singleton connected to the MySQL/MariaDB database. All application and script code uses this directly. The schema is defined in `src/db/schema.ts` with typed columns against the existing database. -- **Schema regeneration**: `pnpm db:schema:generate` reads `prisma/schema.prisma` (for camelCase field names) plus the live DB and rewrites `src/db/schema.ts`. Types for the app come from that committed Drizzle schema — no Prisma client is loaded at runtime. +- **Runtime (Drizzle ORM)**: `@/lib/db` exposes a Drizzle singleton. Schema lives in `src/db/schema.ts`. +- **Schema regeneration**: `pnpm db:schema:generate` reuses field/table names from the previous `src/db/schema.ts` and refreshes column types from the live DB. +- **Drizzle Kit**: studio / generate / introspect for local tooling; CMS apply path remains `pnpm db:migrate`. Use `import { db } from "@/lib/db"` with queries built via `src/db/schema.ts`. @@ -304,7 +302,7 @@ pnpm biome:check # Lint and format 1. Ensure typecheck and tests pass: `pnpm typecheck && pnpm test` 2. Follow existing code conventions (Server Components where possible, minimal client boundaries) 3. Use the `src/lib/motion.ts` animation variants for consistent animations -4. SQL migrations in `prisma/migrations/` must be idempotent +4. SQL migrations in `drizzle/migrations/` must be idempotent 5. For new database code, use the Drizzle runtime directly (`import { db } from "@/lib/db"`) — see [ORM Setup](#4-orm-setup--type-generation) 6. Avoid `any` — use `eslint-disable` or `biome-ignore` comments only when unavoidable diff --git a/drizzle.config.ts b/drizzle.config.ts index 91ebe3f6..605f797c 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,13 +1,16 @@ import "dotenv/config"; import { defineConfig } from "drizzle-kit"; -// The schema is generated by scripts/generate-drizzle-schema.mjs from -// prisma/schema.prisma + live DB introspection. This DB is shared live with the -// Arcturus emulator, so we NEVER run `drizzle-kit migrate`/`push` against it — -// CMS-only schema changes stay in prisma/migrations/*.sql via `pnpm db:migrate`. +// Schema source of truth for the query builder: src/db/schema.ts +// (regenerated via `pnpm db:schema:generate` from the previous schema + live DB). +// +// This DB is shared with the Arcturus emulator — NEVER run `drizzle-kit migrate` +// or `push` against it. CMS DDL stays in drizzle/migrations/*.sql applied by +// `pnpm db:migrate`. Use `pnpm db:generate` only for draft SQL under drizzle/drafts/. export default defineConfig({ dialect: "mysql", schema: "./src/db/schema.ts", + out: "./drizzle/drafts", dbCredentials: { url: process.env.DATABASE_URL ?? "", }, diff --git a/drizzle/drafts/.gitkeep b/drizzle/drafts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/drizzle/drafts/README.md b/drizzle/drafts/README.md new file mode 100644 index 00000000..607a36d1 --- /dev/null +++ b/drizzle/drafts/README.md @@ -0,0 +1 @@ +Draft SQL from `pnpm db:generate` (drizzle-kit). Never apply these automatically — copy reviewed statements into drizzle/migrations/ as numbered CMS migrations, then `pnpm db:migrate`. \ No newline at end of file diff --git a/prisma/migrations/0001_create_website_settings.sql b/drizzle/migrations/0001_create_website_settings.sql similarity index 100% rename from prisma/migrations/0001_create_website_settings.sql rename to drizzle/migrations/0001_create_website_settings.sql diff --git a/prisma/migrations/0002_users_2fa.sql b/drizzle/migrations/0002_users_2fa.sql similarity index 100% rename from prisma/migrations/0002_users_2fa.sql rename to drizzle/migrations/0002_users_2fa.sql diff --git a/prisma/migrations/0003_password_resets.sql b/drizzle/migrations/0003_password_resets.sql similarity index 100% rename from prisma/migrations/0003_password_resets.sql rename to drizzle/migrations/0003_password_resets.sql diff --git a/prisma/migrations/0004_cms_feature_tables.sql b/drizzle/migrations/0004_cms_feature_tables.sql similarity index 100% rename from prisma/migrations/0004_cms_feature_tables.sql rename to drizzle/migrations/0004_cms_feature_tables.sql diff --git a/prisma/migrations/0005_radio_listener_points_columns.sql b/drizzle/migrations/0005_radio_listener_points_columns.sql similarity index 100% rename from prisma/migrations/0005_radio_listener_points_columns.sql rename to drizzle/migrations/0005_radio_listener_points_columns.sql diff --git a/prisma/migrations/0006_writeable_boxes.sql b/drizzle/migrations/0006_writeable_boxes.sql similarity index 100% rename from prisma/migrations/0006_writeable_boxes.sql rename to drizzle/migrations/0006_writeable_boxes.sql diff --git a/prisma/migrations/0007_login_logs.sql b/drizzle/migrations/0007_login_logs.sql similarity index 100% rename from prisma/migrations/0007_login_logs.sql rename to drizzle/migrations/0007_login_logs.sql diff --git a/prisma/migrations/0008_add_bans_user_id_index.sql b/drizzle/migrations/0008_add_bans_user_id_index.sql similarity index 100% rename from prisma/migrations/0008_add_bans_user_id_index.sql rename to drizzle/migrations/0008_add_bans_user_id_index.sql diff --git a/prisma/migrations/0009_radio_contests_giveaways_columns.sql b/drizzle/migrations/0009_radio_contests_giveaways_columns.sql similarity index 100% rename from prisma/migrations/0009_radio_contests_giveaways_columns.sql rename to drizzle/migrations/0009_radio_contests_giveaways_columns.sql diff --git a/prisma/migrations/0010_admin_feature_tables.sql b/drizzle/migrations/0010_admin_feature_tables.sql similarity index 100% rename from prisma/migrations/0010_admin_feature_tables.sql rename to drizzle/migrations/0010_admin_feature_tables.sql diff --git a/prisma/migrations/0011_dynamic_rank_acl.sql b/drizzle/migrations/0011_dynamic_rank_acl.sql similarity index 100% rename from prisma/migrations/0011_dynamic_rank_acl.sql rename to drizzle/migrations/0011_dynamic_rank_acl.sql diff --git a/prisma/migrations/0012_seed_acl_permissions.sql b/drizzle/migrations/0012_seed_acl_permissions.sql similarity index 100% rename from prisma/migrations/0012_seed_acl_permissions.sql rename to drizzle/migrations/0012_seed_acl_permissions.sql diff --git a/prisma/migrations/0013_admin_events_polls_banners_prefixes.sql b/drizzle/migrations/0013_admin_events_polls_banners_prefixes.sql similarity index 100% rename from prisma/migrations/0013_admin_events_polls_banners_prefixes.sql rename to drizzle/migrations/0013_admin_events_polls_banners_prefixes.sql diff --git a/prisma/migrations/0014_complete_acl_and_import_permissions.sql b/drizzle/migrations/0014_complete_acl_and_import_permissions.sql similarity index 100% rename from prisma/migrations/0014_complete_acl_and_import_permissions.sql rename to drizzle/migrations/0014_complete_acl_and_import_permissions.sql diff --git a/prisma/migrations/0015_radio_acl_permissions.sql b/drizzle/migrations/0015_radio_acl_permissions.sql similarity index 100% rename from prisma/migrations/0015_radio_acl_permissions.sql rename to drizzle/migrations/0015_radio_acl_permissions.sql diff --git a/prisma/migrations/0016_housekeeping_group_field.sql b/drizzle/migrations/0016_housekeeping_group_field.sql similarity index 100% rename from prisma/migrations/0016_housekeeping_group_field.sql rename to drizzle/migrations/0016_housekeeping_group_field.sql diff --git a/prisma/migrations/0017_website_jwt_version.sql b/drizzle/migrations/0017_website_jwt_version.sql similarity index 100% rename from prisma/migrations/0017_website_jwt_version.sql rename to drizzle/migrations/0017_website_jwt_version.sql diff --git a/prisma/migrations/0018_acl_repair_admin_nav_grants.sql b/drizzle/migrations/0018_acl_repair_admin_nav_grants.sql similarity index 100% rename from prisma/migrations/0018_acl_repair_admin_nav_grants.sql rename to drizzle/migrations/0018_acl_repair_admin_nav_grants.sql diff --git a/prisma/migrations/0019_add_website_login_logs.sql b/drizzle/migrations/0019_add_website_login_logs.sql similarity index 100% rename from prisma/migrations/0019_add_website_login_logs.sql rename to drizzle/migrations/0019_add_website_login_logs.sql diff --git a/package.json b/package.json index f35ae01f..fdfab3c5 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "dev": "next dev", "build": "next build", "start": "next start", - "prisma:generate": "prisma generate", "typecheck": "tsc --noEmit --incremental", "biome:check": "biome check --write .", "biome:lint": "biome lint .", @@ -21,6 +20,9 @@ "db:migrate": "tsx scripts/apply-migrations.ts", "db:migrate:status": "tsx scripts/apply-migrations.ts --status", "db:schema:generate": "node scripts/generate-drizzle-schema.mjs", + "db:generate": "drizzle-kit generate", + "db:studio": "drizzle-kit studio", + "db:introspect": "drizzle-kit introspect", "jobs:worker": "tsx scripts/jobs-worker.ts", "prepare": "husky" }, @@ -76,7 +78,6 @@ "devDependencies": { "@biomejs/biome": "2.5.6", "@next/bundle-analyzer": "^16.2.12", - "@prisma/client": "^7.9.1", "@tailwindcss/forms": "^0.5.11", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/typography": "^0.5.20", @@ -92,7 +93,6 @@ "lint-staged": "^17.3.0", "pino-pretty": "^13.1.3", "postcss": "^8.5.25", - "prisma": "^7.9.1", "tailwindcss": "^4.3.3", "tsx": "^4.23.1", "typescript": "^7.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee5fcef8..49b68ee4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,9 +144,6 @@ importers: '@next/bundle-analyzer': specifier: ^16.2.12 version: 16.2.12 - '@prisma/client': - specifier: ^7.9.1 - version: 7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2) '@tailwindcss/forms': specifier: ^0.5.11 version: 0.5.11(tailwindcss@4.3.3) @@ -192,9 +189,6 @@ importers: postcss: specifier: ^8.5.25 version: 8.5.25 - prisma: - specifier: ^7.9.1 - version: 7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2) tailwindcss: specifier: ^4.3.3 version: 4.3.3 @@ -5275,12 +5269,15 @@ snapshots: '@electric-sql/pglite-socket@0.1.3(@electric-sql/pglite@0.4.3)': dependencies: '@electric-sql/pglite': 0.4.3 + optional: true '@electric-sql/pglite-tools@0.3.3(@electric-sql/pglite@0.4.3)': dependencies: '@electric-sql/pglite': 0.4.3 + optional: true - '@electric-sql/pglite@0.4.3': {} + '@electric-sql/pglite@0.4.3': + optional: true '@emnapi/core@1.11.2': dependencies: @@ -6053,7 +6050,8 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/client-runtime-utils@7.9.1': {} + '@prisma/client-runtime-utils@7.9.1': + optional: true '@prisma/client@7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2)': dependencies: @@ -6061,6 +6059,7 @@ snapshots: optionalDependencies: prisma: 7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(magicast@0.5.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2) typescript: 7.0.2 + optional: true '@prisma/config@7.9.1(magicast@0.5.3)': dependencies: @@ -6070,10 +6069,13 @@ snapshots: empathic: 2.0.0 transitivePeerDependencies: - magicast + optional: true - '@prisma/debug@7.2.0': {} + '@prisma/debug@7.2.0': + optional: true - '@prisma/debug@7.9.1': {} + '@prisma/debug@7.9.1': + optional: true '@prisma/dev@0.24.17(typescript@7.0.2)': dependencies: @@ -6094,8 +6096,10 @@ snapshots: zeptomatch: 2.1.0 transitivePeerDependencies: - typescript + optional: true - '@prisma/engines-version@7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad': {} + '@prisma/engines-version@7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad': + optional: true '@prisma/engines@7.9.1': dependencies: @@ -6103,22 +6107,27 @@ snapshots: '@prisma/engines-version': 7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad '@prisma/fetch-engine': 7.9.1 '@prisma/get-platform': 7.9.1 + optional: true '@prisma/fetch-engine@7.9.1': dependencies: '@prisma/debug': 7.9.1 '@prisma/engines-version': 7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad '@prisma/get-platform': 7.9.1 + optional: true '@prisma/get-platform@7.2.0': dependencies: '@prisma/debug': 7.2.0 + optional: true '@prisma/get-platform@7.9.1': dependencies: '@prisma/debug': 7.9.1 + optional: true - '@prisma/query-plan-executor@7.2.0': {} + '@prisma/query-plan-executor@7.2.0': + optional: true '@prisma/streams-local@0.1.11': dependencies: @@ -6126,6 +6135,7 @@ snapshots: better-result: 2.10.0 env-paths: 3.0.0 proper-lockfile: 4.1.2 + optional: true '@prisma/studio-core@0.33.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: @@ -6145,8 +6155,10 @@ snapshots: react-dom: 19.2.8(react@19.2.8) transitivePeerDependencies: - '@types/react-dom' + optional: true - '@radix-ui/primitive@1.1.3': {} + '@radix-ui/primitive@1.1.3': + optional: true '@radix-ui/primitive@1.1.7': {} @@ -6155,6 +6167,7 @@ snapshots: react: 19.2.8 optionalDependencies: '@types/react': 19.2.18 + optional: true '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6264,6 +6277,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) + optional: true '@radix-ui/react-slot@1.2.3(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6271,6 +6285,7 @@ snapshots: react: 19.2.8 optionalDependencies: '@types/react': 19.2.18 + optional: true '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6289,6 +6304,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) + optional: true '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6303,6 +6319,7 @@ snapshots: react: 19.2.8 optionalDependencies: '@types/react': 19.2.18 + optional: true '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6319,6 +6336,7 @@ snapshots: react: 19.2.8 optionalDependencies: '@types/react': 19.2.18 + optional: true '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6332,6 +6350,7 @@ snapshots: react: 19.2.8 optionalDependencies: '@types/react': 19.2.18 + optional: true '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -6871,45 +6890,58 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/d3-array@3.0.3': {} + '@types/d3-array@3.0.3': + optional: true - '@types/d3-color@3.1.0': {} + '@types/d3-color@3.1.0': + optional: true - '@types/d3-delaunay@6.0.1': {} + '@types/d3-delaunay@6.0.1': + optional: true - '@types/d3-format@3.0.1': {} + '@types/d3-format@3.0.1': + optional: true '@types/d3-geo@3.1.0': dependencies: '@types/geojson': 7946.0.16 + optional: true '@types/d3-interpolate@3.0.1': dependencies: '@types/d3-color': 3.1.0 + optional: true - '@types/d3-path@3.1.1': {} + '@types/d3-path@3.1.1': + optional: true '@types/d3-scale@4.0.2': dependencies: '@types/d3-time': 3.0.0 + optional: true '@types/d3-shape@3.1.7': dependencies: '@types/d3-path': 3.1.1 + optional: true - '@types/d3-time-format@2.1.0': {} + '@types/d3-time-format@2.1.0': + optional: true - '@types/d3-time@3.0.0': {} + '@types/d3-time@3.0.0': + optional: true '@types/deep-eql@4.0.2': {} '@types/estree@1.0.9': {} - '@types/geojson@7946.0.16': {} + '@types/geojson@7946.0.16': + optional: true '@types/json-schema@7.0.15': {} - '@types/lodash@4.17.24': {} + '@types/lodash@4.17.24': + optional: true '@types/node@26.1.2': dependencies: @@ -6989,11 +7021,13 @@ snapshots: '@visx/curve@4.0.1-alpha.0': dependencies: '@visx/vendor': 4.0.0-alpha.0 + optional: true '@visx/event@4.0.1-alpha.0': dependencies: '@types/react': 19.2.18 '@visx/point': 4.0.1-alpha.0 + optional: true '@visx/grid@4.0.1-alpha.0(react@19.2.8)': dependencies: @@ -7005,14 +7039,17 @@ snapshots: '@visx/shape': 4.0.1-alpha.0(react@19.2.8) classnames: 2.5.1 react: 19.2.8 + optional: true '@visx/group@4.0.1-alpha.0(react@19.2.8)': dependencies: '@types/react': 19.2.18 classnames: 2.5.1 react: 19.2.8 + optional: true - '@visx/point@4.0.1-alpha.0': {} + '@visx/point@4.0.1-alpha.0': + optional: true '@visx/responsive@4.0.1-alpha.0(react@19.2.8)': dependencies: @@ -7020,10 +7057,12 @@ snapshots: '@types/react': 19.2.18 lodash: 4.18.1 react: 19.2.8 + optional: true '@visx/scale@4.0.1-alpha.0': dependencies: '@visx/vendor': 4.0.0-alpha.0 + optional: true '@visx/shape@4.0.1-alpha.0(react@19.2.8)': dependencies: @@ -7036,6 +7075,7 @@ snapshots: classnames: 2.5.1 lodash: 4.18.1 react: 19.2.8 + optional: true '@visx/vendor@4.0.0-alpha.0': dependencies: @@ -7062,6 +7102,7 @@ snapshots: d3-time: 3.1.0 d3-time-format: 4.1.0 internmap: 2.0.3 + optional: true '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': dependencies: @@ -7262,7 +7303,8 @@ snapshots: baseline-browser-mapping@2.11.6: {} - better-result@2.10.0: {} + better-result@2.10.0: + optional: true bidi-js@1.0.3: dependencies: @@ -7298,6 +7340,7 @@ snapshots: rc9: 3.0.1 optionalDependencies: magicast: 0.5.3 + optional: true caniuse-lite@1.0.30001806: {} @@ -7306,6 +7349,7 @@ snapshots: chokidar@5.0.0: dependencies: readdirp: 5.0.0 + optional: true chrome-trace-event@1.0.4: {} @@ -7315,7 +7359,8 @@ snapshots: dependencies: clsx: 2.1.1 - classnames@2.5.1: {} + classnames@2.5.1: + optional: true client-only@0.0.1: {} @@ -7349,7 +7394,8 @@ snapshots: commondir@1.0.1: {} - confbox@0.2.4: {} + confbox@0.2.4: + optional: true content-type@2.0.0: {} @@ -7377,28 +7423,36 @@ snapshots: d3-array@3.2.1: dependencies: internmap: 2.0.3 + optional: true d3-array@3.2.4: dependencies: internmap: 2.0.3 + optional: true - d3-color@3.1.0: {} + d3-color@3.1.0: + optional: true d3-delaunay@6.0.2: dependencies: delaunator: 5.1.0 + optional: true - d3-format@3.1.0: {} + d3-format@3.1.0: + optional: true d3-geo@3.1.0: dependencies: d3-array: 3.2.4 + optional: true d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + optional: true - d3-path@3.1.0: {} + d3-path@3.1.0: + optional: true d3-scale@4.0.2: dependencies: @@ -7407,18 +7461,22 @@ snapshots: d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 + optional: true d3-shape@3.2.0: dependencies: d3-path: 3.1.0 + optional: true d3-time-format@4.1.0: dependencies: d3-time: 3.1.0 + optional: true d3-time@3.1.0: dependencies: d3-array: 3.2.4 + optional: true data-urls@7.0.0(@noble/hashes@2.2.0): dependencies: @@ -7437,17 +7495,21 @@ snapshots: decimal.js@10.6.0: {} - deepmerge-ts@7.1.5: {} + deepmerge-ts@7.1.5: + optional: true - defu@6.1.7: {} + defu@6.1.7: + optional: true delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 + optional: true denque@2.1.0: {} - destr@2.0.5: {} + destr@2.0.5: + optional: true detect-libc@2.1.2: {} @@ -7485,16 +7547,19 @@ snapshots: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 + optional: true electron-to-chromium@1.5.397: {} - elkjs@0.11.1: {} + elkjs@0.11.1: + optional: true emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - empathic@2.0.0: {} + empathic@2.0.0: + optional: true end-of-stream@1.4.5: dependencies: @@ -7507,7 +7572,8 @@ snapshots: entities@8.0.0: {} - env-paths@3.0.0: {} + env-paths@3.0.0: + optional: true es-module-lexer@2.3.1: {} @@ -7625,21 +7691,25 @@ snapshots: expect-type@1.4.0: {} - exsolve@1.1.1: {} + exsolve@1.1.1: + optional: true fast-check@3.23.2: dependencies: pure-rand: 6.1.0 + optional: true fast-copy@4.0.4: {} - fast-decode-uri-component@1.0.1: {} + fast-decode-uri-component@1.0.1: + optional: true fast-deep-equal@3.1.3: {} fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 + optional: true fast-safe-stringify@2.1.1: {} @@ -7669,6 +7739,7 @@ snapshots: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 safe-regex2: 5.1.1 + optional: true find-up@5.0.0: dependencies: @@ -7707,13 +7778,15 @@ snapshots: get-nonce@1.0.1: {} - get-port-please@3.2.0: {} + get-port-please@3.2.0: + optional: true get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 - giget@3.3.1: {} + giget@3.3.1: + optional: true glob@10.5.0: dependencies: @@ -7726,9 +7799,11 @@ snapshots: graceful-fs@4.2.11: {} - grammex@3.1.13: {} + grammex@3.1.13: + optional: true - graphmatch@1.1.1: {} + graphmatch@1.1.1: + optional: true gzip-size@6.0.0: dependencies: @@ -7777,7 +7852,8 @@ snapshots: inherits@2.0.4: {} - internmap@2.0.3: {} + internmap@2.0.3: + optional: true intl-messageformat@11.2.12: dependencies: @@ -8036,7 +8112,8 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash@4.18.1: {} + lodash@4.18.1: + optional: true long@5.3.2: {} @@ -8147,6 +8224,7 @@ snapshots: named-placeholders: 1.1.6 seq-queue: 0.0.5 sqlstring: 2.3.3 + optional: true mysql2@3.23.2(@types/node@26.1.2): dependencies: @@ -8235,7 +8313,8 @@ snapshots: obug@2.1.4: {} - ohash@2.0.11: {} + ohash@2.0.11: + optional: true on-exit-leak-free@2.1.2: {} @@ -8328,7 +8407,8 @@ snapshots: pathe@2.0.3: {} - perfect-debounce@2.1.0: {} + perfect-debounce@2.1.0: + optional: true picocolors@1.1.1: {} @@ -8375,6 +8455,7 @@ snapshots: confbox: 0.2.4 exsolve: 1.1.1 pathe: 2.0.3 + optional: true playwright-core@1.62.0: optional: true @@ -8401,7 +8482,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgres@3.4.7: {} + postgres@3.4.7: + optional: true preact-render-to-string@6.5.11(preact@10.24.3): dependencies: @@ -8425,6 +8507,7 @@ snapshots: - magicast - react - react-dom + optional: true process-nextick-args@2.0.1: {} @@ -8437,6 +8520,7 @@ snapshots: graceful-fs: 4.2.11 retry: 0.12.0 signal-exit: 3.0.7 + optional: true proxy-from-env@1.1.0: {} @@ -8447,7 +8531,8 @@ snapshots: punycode@2.3.1: {} - pure-rand@6.1.0: {} + pure-rand@6.1.0: + optional: true quick-format-unescaped@4.0.4: {} @@ -8455,6 +8540,7 @@ snapshots: dependencies: defu: 6.1.7 destr: 2.0.5 + optional: true react-dom@19.2.8(react@19.2.8): dependencies: @@ -8504,7 +8590,8 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readdirp@5.0.0: {} + readdirp@5.0.0: + optional: true real-require@0.2.0: {} @@ -8516,7 +8603,8 @@ snapshots: dependencies: redis-errors: 1.2.0 - remeda@2.33.4: {} + remeda@2.33.4: + optional: true require-from-string@2.0.2: {} @@ -8536,11 +8624,14 @@ snapshots: resolve-pkg-maps@1.0.0: {} - ret@0.5.0: {} + ret@0.5.0: + optional: true - retry@0.12.0: {} + retry@0.12.0: + optional: true - robust-predicates@3.0.3: {} + robust-predicates@3.0.3: + optional: true rolldown@1.2.1: dependencies: @@ -8599,6 +8690,7 @@ snapshots: safe-regex2@5.1.1: dependencies: ret: 0.5.0 + optional: true safe-stable-stringify@2.5.0: {} @@ -8625,7 +8717,8 @@ snapshots: semver@7.8.5: {} - seq-queue@0.0.5: {} + seq-queue@0.0.5: + optional: true server-only@0.0.1: {} @@ -8672,7 +8765,8 @@ snapshots: siginfo@2.0.0: {} - signal-exit@3.0.7: {} + signal-exit@3.0.7: + optional: true signal-exit@4.1.0: {} @@ -8706,7 +8800,8 @@ snapshots: sql-escaper@1.5.1: {} - sqlstring@2.3.3: {} + sqlstring@2.3.3: + optional: true stackback@0.0.2: {} @@ -8721,7 +8816,8 @@ snapshots: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 - std-env@3.10.0: {} + std-env@3.10.0: + optional: true std-env@4.2.0: {} @@ -8909,6 +9005,7 @@ snapshots: valibot@1.4.2(typescript@7.0.2): optionalDependencies: typescript: 7.0.2 + optional: true vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0): dependencies: @@ -9091,5 +9188,6 @@ snapshots: dependencies: grammex: 3.1.13 graphmatch: 1.1.1 + optional: true zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4f5d8722..cc9a18b2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,9 +3,6 @@ # pnpm v11 vervanger voor onlyBuiltDependencies allowBuilds: esbuild: true - prisma: true - "@prisma/client": true - "@prisma/engines": true sharp: true "@parcel/watcher": true "@swc/core": true diff --git a/prisma.config.ts b/prisma.config.ts deleted file mode 100644 index d3152ad7..00000000 --- a/prisma.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import "dotenv/config"; -import { defineConfig, env } from "prisma/config"; - -// Prisma 7 config. The datasource URL lives here (not in schema.prisma). -// We NEVER run `prisma migrate`/`db push` against this database — it is shared -// live with the Arcturus emulator. CMS-only schema changes go in -// prisma/migrations/*.sql (idempotent) applied via `pnpm db:migrate`. -export default defineConfig({ - schema: "prisma/schema.prisma", - migrations: { - path: "prisma/migrations", - }, - datasource: { - url: env("DATABASE_URL"), - }, -}); diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index 770cf79b..00000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,2457 +0,0 @@ -// AtomCMS → Next.js conversion data layer. -// -// The database schema is OWNED by the Arcturus Morningstar emulator and shared -// live with the game server. This app conforms to that schema — it never -// generates or migrates the emulator tables. CMS-only tables (website_*) are -// created via idempotent SQL in prisma/migrations/*.sql (see apply-migrations). -// -// This is the first slice (users / users_currency / bans / website_settings); -// the full table set will be regenerated via `prisma db pull` once a live DB -// connection is available. - -generator client { - provider = "prisma-client" - output = "../src/generated/prisma" -} - -datasource db { - provider = "mysql" -} - -enum users_gender { - M - F -} - -enum bans_type { - account - ip - machine - super -} - -/// Emulator-owned core account table (users). Column names mirror default.sql. -model User { - id Int @id @default(autoincrement()) - username String @unique(map: "username") @db.VarChar(25) - realName String @default("KREWS DEV") @map("real_name") @db.VarChar(25) - password String @db.VarChar(255) - mail String? @db.VarChar(500) - mailVerified String @default("0") @map("mail_verified") - accountCreated Int @map("account_created") - accountDayOfBirth Int @default(0) @map("account_day_of_birth") - lastLogin Int @default(0) @map("last_login") - lastOnline Int @default(0) @map("last_online") - motto String @default("") @db.VarChar(127) - look String @default("") @db.VarChar(256) - gender users_gender @default(M) - rank Int @default(1) - credits Int @default(2500) - pixels Int @default(500) - points Int @default(10) - online String @default("0") - // Ephemeral SSO ticket rotated on each emulator handshake ({HOTEL_NAME}-{uuid}). - // The emulator validates it byte-for-byte when the Nitro/Flash client connects. - authTicket String @default("") @map("auth_ticket") @db.VarChar(256) - ipRegister String @map("ip_register") @db.VarChar(45) - ipCurrent String @map("ip_current") @db.VarChar(45) - machineId String @default("") @map("machine_id") @db.VarChar(64) - homeRoom Int @default(0) @map("home_room") - secretKey String? @map("secret_key") @db.VarChar(40) - pincode String? @db.VarChar(11) - extraRank Int? @map("extra_rank") - // CMS-added (Laravel Fortify) 2FA columns on the users table. - twoFactorSecret String? @map("two_factor_secret") @db.Text - twoFactorRecoveryCodes String? @map("two_factor_recovery_codes") @db.Text - twoFactorConfirmedAt DateTime? @map("two_factor_confirmed_at") @db.Timestamp(0) - // Bumped to invalidate all CMS JWT sessions ("sign out everywhere"). - websiteJwtVersion Int @default(0) @map("website_jwt_version") - - createdTickets WebsiteTicket[] @relation("ticketCreator") - assignedTickets WebsiteTicket[] @relation("ticketAssignee") - ticketMessages WebsiteTicketMessage[] - auditLogs AdminAuditLog[] - - @@map("users") -} - -/// Emulator-owned multi-currency ledger. Composite PK (user_id, type), no id. -/// Currency types are fixed: Credits=-1, Duckets=0, Diamonds=5, Points=101. -model UsersCurrency { - userId Int @map("user_id") - type Int - amount Int @default(0) - - @@id([userId, type]) - @@map("users_currency") -} - -/// Emulator-owned bans table. Active bans are those with ban_expire > now(). -model Ban { - id Int @id @default(autoincrement()) - userId Int @map("user_id") - ip String @default("") @db.VarChar(50) - machineId String @default("") @map("machine_id") @db.VarChar(255) - userStaffId Int @map("user_staff_id") - timestamp Int - banExpire Int @default(0) @map("ban_expire") - banReason String @default("") @map("ban_reason") @db.VarChar(200) - type bans_type @default(account) - cfhTopic Int @default(-1) @map("cfh_topic") - - @@index([userId]) - @@map("bans") -} - -/// CMS-owned key/value settings (created by prisma/migrations, not the emulator). -model WebsiteSetting { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - key String @unique(map: "website_settings_key_unique") @db.VarChar(255) - value String @db.Text - comment String? @db.VarChar(255) - - @@map("website_settings") -} - -/// CMS-owned password reset tokens (Laravel-compatible table). Created via -/// prisma/migrations if absent. -model PasswordReset { - email String @id @db.VarChar(255) - token String @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - - @@map("password_resets") -} - -// === GENERATED MODELS (assembled from default.sql + Laravel migrations) === -// 186 tables. Regenerated by assemble-schema.mjs — edit the -// generator instead of hand-editing below. - -model Achievements { - id Int @unique @default(autoincrement()) - name String @db.VarChar(64) @default("ACH_") - category String @db.VarChar(255) @default("identity") - level Int @default(1) - rewardAmount Int @map("reward_amount") @default(100) - rewardType Int @map("reward_type") @default(0) - points Int? @default(10) - progressNeeded Int @map("progress_needed") @default(1) - - @@id([name, level]) - @@map("achievements") -} - - -model CalendarCampaigns { - id Int @id @default(autoincrement()) - name String @db.VarChar(255) @default("") - image String @db.VarChar(255) @default("") - startTimestamp Int @map("start_timestamp") @default(0) - totalDays Int @map("total_days") @default(30) - lockExpired String @map("lock_expired") @db.VarChar(255) @default("1") - enabled String @db.VarChar(255) @default("1") - - @@map("calendar_campaigns") -} - -model CalendarRewards { - id Int @id @default(autoincrement()) - campaignId Int @map("campaign_id") @default(0) - productName String @map("product_name") @db.VarChar(128) @default("") - customImage String @map("custom_image") @db.VarChar(128) @default("") - credits Int @default(0) - pixels Int @default(0) - points Int @default(0) - pointsType Int @map("points_type") @default(0) - badge String @db.VarChar(25) @default("") - itemId Int @map("item_id") @default(0) - subscriptionType String? @map("subscription_type") @db.VarChar(128) @default("") - subscriptionDays Int @map("subscription_days") @default(0) - - @@map("calendar_rewards") -} - - -model CameraWeb { - id Int @id @default(autoincrement()) - userId Int @map("user_id") - roomId Int @map("room_id") @default(0) - timestamp Int - url String @db.VarChar(128) @default("") - - @@map("camera_web") -} - - -model CatalogItems { - id Int @id @default(autoincrement()) - itemIds String @map("item_ids") @db.VarChar(666) - pageId Int @map("page_id") - catalogName String @map("catalog_name") @db.VarChar(100) - costCredits Int @map("cost_credits") @default(3) - costPoints Int @map("cost_points") @default(0) - pointsType Int @map("points_type") @default(0) - amount Int @default(1) - limitedStack Int @map("limited_stack") @default(0) - limitedSells Int @map("limited_sells") @default(0) - orderNumber Int @map("order_number") @default(1) - offerId Int @map("offer_id") @default(-1) - songId Int @map("song_id") @default(0) - extradata String @db.VarChar(500) @default("") - haveOffer String @db.VarChar(255) @default("1") - clubOnly String @map("club_only") @db.VarChar(255) @default("0") - - @@map("catalog_items") -} - -model CatalogItemsBc { - id Int @id @default(autoincrement()) - itemIds String @map("item_ids") @db.VarChar(666) - pageId Int @map("page_id") - catalogName String @map("catalog_name") @db.VarChar(100) - orderNumber Int @map("order_number") @default(1) - extradata String @db.VarChar(500) @default("") - - @@map("catalog_items_bc") -} - - -model CatalogPages { - id Int @id @default(autoincrement()) - parentId Int @map("parent_id") @default(-1) - captionSave String @map("caption_save") @db.VarChar(25) @default("") - caption String @db.VarChar(128) - pageLayout String @map("page_layout") @db.VarChar(37) @default("default_3x3") - iconColor Int @map("icon_color") @default(1) - iconImage Int @map("icon_image") @default(1) - minRank Int @map("min_rank") @default(1) - orderNum Int @map("order_num") @default(1) - visible String @db.VarChar(1) @default("1") - enabled String @db.VarChar(1) @default("1") - clubOnly String @map("club_only") @db.VarChar(1) @default("0") - vipOnly String @map("vip_only") @db.VarChar(1) @default("0") - pageHeadline String @map("page_headline") @db.VarChar(1024) @default("") - pageTeaser String @map("page_teaser") @db.VarChar(64) @default("") - pageSpecial String? @map("page_special") @db.VarChar(2048) - pageText1 String? @map("page_text1") @db.Text - pageText2 String? @map("page_text2") @db.Text - pageTextDetails String? @map("page_text_details") @db.Text - pageTextTeaser String? @map("page_text_teaser") @db.Text - roomId Int? @map("room_id") @default(0) - includes String @db.VarChar(128) @default("") - - @@map("catalog_pages") -} - -model CatalogPagesBc { - id Int @id @default(autoincrement()) - parentId Int @map("parent_id") @default(-1) - caption String @db.VarChar(128) - pageLayout String @map("page_layout") @db.VarChar(37) @default("default_3x3") - iconColor Int @map("icon_color") @default(1) - iconImage Int @map("icon_image") @default(1) - orderNum Int @map("order_num") @default(1) - visible String @db.VarChar(1) @default("1") - enabled String @db.VarChar(1) @default("1") - pageHeadline String @map("page_headline") @db.VarChar(1024) @default("") - pageTeaser String @map("page_teaser") @db.VarChar(64) @default("") - pageSpecial String? @map("page_special") @db.VarChar(2048) - pageText1 String? @map("page_text1") @db.Text - pageText2 String? @map("page_text2") @db.Text - pageTextDetails String? @map("page_text_details") @db.Text - pageTextTeaser String? @map("page_text_teaser") @db.Text - - @@map("catalog_pages_bc") -} - - -model EmulatorErrors { - id Int @id @default(autoincrement()) - timestamp Int @default(0) - version String @db.VarChar(64) - buildHash String @map("build_hash") @db.VarChar(64) - type String @db.VarChar(32) @default("Exception") - stacktrace Bytes - - @@map("emulator_errors") -} - -model EmulatorSettings { - key String @id @db.VarChar(100) - value String @db.VarChar(512) - - @@map("emulator_settings") -} - -model EmulatorTexts { - key String @id @db.VarChar(100) - value String @db.VarChar(4096) - - @@map("emulator_texts") -} - - -model Guilds { - id Int @id @default(autoincrement()) - userId Int @default(0) @map("user_id") - name String @db.VarChar(50) @default("") - description String @db.VarChar(250) @default("") - roomId Int @default(0) @map("room_id") - state Int @default(0) - rights String @default("0") - colorOne Int @default(0) @map("color_one") - colorTwo Int @default(0) @map("color_two") - badge String @db.VarChar(256) @default("") - dateCreated Int @map("date_created") - forum String @default("0") - readForum String @default("EVERYONE") @map("read_forum") - postMessages String @default("EVERYONE") @map("post_messages") - postThreads String @default("EVERYONE") @map("post_threads") - modForum String @default("ADMINS") @map("mod_forum") - - @@map("guilds") -} - - -model GuildsForumsComments { - id Int @id @default(autoincrement()) @db.UnsignedInt - threadId Int @default(0) @map("thread_id") - userId Int @default(0) @map("user_id") - message String @db.Text - createdAt Int @default(0) @map("created_at") - state Int @default(0) - adminId Int @default(0) @map("admin_id") - - @@map("guilds_forums_comments") -} - -model GuildsForumsThreads { - id Int @id @default(autoincrement()) @db.UnsignedInt - guildId Int? @default(0) @map("guild_id") - openerId Int? @default(0) @map("opener_id") - subject String? @db.VarChar(255) @default("") - postsCount Int? @default(0) @map("posts_count") - createdAt Int? @default(0) @map("created_at") - updatedAt Int? @default(0) @map("updated_at") - state Int? @default(0) - pinned Int? @default(0) - locked Int? @default(0) - adminId Int? @default(0) @map("admin_id") - - @@map("guilds_forums_threads") -} - -model GuildsMembers { - id Int @id @default(autoincrement()) - guildId Int @default(0) @map("guild_id") - userId Int @default(0) @map("user_id") - levelId Int @default(0) @map("level_id") - memberSince Int @default(0) @map("member_since") - - @@map("guilds_members") -} - - -model Items { - id Int @id @default(autoincrement()) - userId Int @default(0) @map("user_id") - roomId Int @default(0) @map("room_id") - itemId Int? @default(0) @map("item_id") - wallPos String @db.VarChar(20) @default("") @map("wall_pos") - x Int @default(0) - y Int @default(0) - z Float @default(0.000000) @db.Double - rot Int @default(0) - extraData String @db.VarChar(1024) @default("") @map("extra_data") - wiredData String @db.VarChar(10000) @default("") @map("wired_data") - limitedData String @db.VarChar(10) @default("0:0") @map("limited_data") - guildId Int @default(0) @map("guild_id") - - @@map("items") -} - -model ItemsBase { - id Int @id @default(autoincrement()) @db.UnsignedInt - spriteId Int @default(0) @map("sprite_id") - publicName String @db.VarChar(56) @default("") @map("public_name") - itemName String @db.VarChar(70) @map("item_name") - type String @db.VarChar(3) @default("s") - width Int @default(1) - length Int @default(1) - stackHeight Decimal @default(0.00) @db.Decimal(4, 2) @map("stack_height") - allowStack Int @default(1) @map("allow_stack") - allowSit Int @default(0) @map("allow_sit") - allowLay Int @default(0) @map("allow_lay") - allowWalk Int @default(0) @map("allow_walk") - allowGift Int @default(1) @map("allow_gift") - allowTrade Int @default(1) @map("allow_trade") - allowRecycle Int @default(0) @map("allow_recycle") - allowMarketplaceSell Int @default(0) @map("allow_marketplace_sell") - allowInventoryStack Int @default(1) @map("allow_inventory_stack") - interactionType String @db.VarChar(500) @default("default") @map("interaction_type") - interactionModesCount Int @default(1) @map("interaction_modes_count") - vendingIds String @db.VarChar(255) @default("0") @map("vending_ids") - multiheight String @db.VarChar(50) @default("0") - customparams String @db.VarChar(256) @default("") - effectIdMale Int @default(0) @map("effect_id_male") - effectIdFemale Int @default(0) @map("effect_id_female") - clothingOnWalk String @db.VarChar(255) @default("") @map("clothing_on_walk") - - @@map("items_base") -} - - -model LogsShopPurchases { - id Int @id @default(autoincrement()) - timestamp Int? - userId Int? @map("user_id") - catalogItemId Int? @map("catalog_item_id") - itemIds String? @db.Text @map("item_ids") - catalogName String? @db.VarChar(255) @map("catalog_name") - costCredits Int? @map("cost_credits") - costPoints Int? @map("cost_points") - pointsType Int? @map("points_type") - amount Int? - - @@map("logs_shop_purchases") -} - -model MarketplaceItems { - id Int @id @default(autoincrement()) - itemId Int @map("item_id") - userId Int @map("user_id") - price Int - timestamp Int - soldTimestamp Int @default(0) @map("sold_timestamp") - state Int @default(1) - - @@map("marketplace_items") -} - - -model MessengerFriendrequests { - id Int @id @default(autoincrement()) - userToId Int @default(0) @map("user_to_id") - userFromId Int @default(0) @map("user_from_id") - - @@map("messenger_friendrequests") -} - -model MessengerFriendships { - id Int @id @default(autoincrement()) - userOneId Int @default(0) @map("user_one_id") - userTwoId Int @default(0) @map("user_two_id") - relation Int @default(0) - friendsSince Int @default(0) @map("friends_since") - category Int @default(0) - - @@map("messenger_friendships") -} - -model MessengerOffline { - id Int @id @default(autoincrement()) - userId Int @default(0) @map("user_id") - userFromId Int @default(0) @map("user_from_id") - message String @db.VarChar(500) - sendedOn Int @map("sended_on") - - @@map("messenger_offline") -} - - -model NavigatorFlatcats { - id Int @id @default(autoincrement()) - minRank Int @default(0) @map("min_rank") - captionSave String @default("caption_save") @db.VarChar(32) @map("caption_save") - caption String @db.VarChar(100) - canTrade String @default("1") - maxUserCount Int @default(100) @map("max_user_count") - public String @default("0") - listType Int @default(0) @map("list_type") - orderNum Int @default(0) @map("order_num") - - @@map("navigator_flatcats") -} - -model NavigatorPubliccats { - id Int @id @default(autoincrement()) - name String @default("Staff Picks") @db.VarChar(32) - image String @default("0") - visible String @default("1") - orderNum Int @default(0) @map("order_num") - - @@map("navigator_publiccats") -} - - -model RoomTradeLog { - id Int @id @default(autoincrement()) - userOneId Int @map("user_one_id") - userTwoId Int @map("user_two_id") - userOneIp String @map("user_one_ip") @db.VarChar(45) - userTwoIp String @map("user_two_ip") @db.VarChar(45) - timestamp Int - userOneItemCount Int @map("user_one_item_count") - userTwoItemCount Int @map("user_two_item_count") - - @@map("room_trade_log") -} - - -model Rooms { - id Int @id @default(autoincrement()) - ownerId Int @default(0) @map("owner_id") - ownerName String @db.VarChar(25) @default("") @map("owner_name") - name String @db.VarChar(50) @default("") - description String @db.VarChar(512) @default("") - model String @db.VarChar(20) @default("model_a") - password String @db.VarChar(20) @default("") - state String @default("open") - users Int @default(0) - usersMax Int @default(25) @map("users_max") - guildId Int @default(0) @map("guild_id") - category Int @default(1) - score Int @default(0) - paperFloor String @db.VarChar(5) @default("0.0") @map("paper_floor") - paperWall String @db.VarChar(5) @default("0.0") @map("paper_wall") - paperLandscape String @db.VarChar(5) @default("0.0") @map("paper_landscape") - thicknessWall Int @default(0) @map("thickness_wall") - wallHeight Int @default(-1) @map("wall_height") - thicknessFloor Int @default(0) @map("thickness_floor") - moodlightData String @db.VarChar(254) @default("2,1,1,#000000,255;2,3,1,#000000,255;2,3,1,#000000,255;") @map("moodlight_data") - tags String @db.VarChar(500) @default("") - isPublic String @default("0") @map("is_public") - isStaffPicked String @default("0") @map("is_staff_picked") - allowOtherPets String @default("0") @map("allow_other_pets") - allowOtherPetsEat String @default("0") @map("allow_other_pets_eat") - allowWalkthrough String @default("1") @map("allow_walkthrough") - allowHidewall String @default("0") @map("allow_hidewall") - chatMode Int @default(0) @map("chat_mode") - chatWeight Int @default(1) @map("chat_weight") - chatSpeed Int @default(1) @map("chat_speed") - chatHearingDistance Int @default(50) @map("chat_hearing_distance") - chatProtection Int @default(2) @map("chat_protection") - overrideModel String @default("0") @map("override_model") - whoCanMute Int @default(0) @map("who_can_mute") - whoCanKick Int @default(0) @map("who_can_kick") - whoCanBan Int @default(0) @map("who_can_ban") - pollId Int @default(0) @map("poll_id") - rollerSpeed Int @default(4) @map("roller_speed") - promoted String @default("0") - tradeMode Int @default(2) @map("trade_mode") - moveDiagonally String @default("1") @map("move_diagonally") - jukeboxActive String @default("0") @map("jukebox_active") - hidewired String @default("0") - isForSale String @default("0") @map("is_forsale") - - @@map("rooms") -} - - -model Soundtracks { - id Int @id @default(autoincrement()) - code String @db.VarChar(32) - name String @db.VarChar(100) - author String @db.VarChar(50) - track String @db.Text - length Int @default(0) - - @@map("soundtracks") -} - - -model SupportTickets { - id Int @id @default(autoincrement()) - state Int @default(0) - type Int @default(1) - timestamp Int @default(0) - score Int @default(0) - senderId Int @default(0) @map("sender_id") - reportedId Int @default(0) @map("reported_id") - roomId Int @default(0) @map("room_id") - modId Int @default(0) @map("mod_id") - issue String @db.VarChar(500) @default("") - category Int @default(0) - groupId Int @map("group_id") - threadId Int @map("thread_id") - commentId Int @map("comment_id") - photoItemId Int @default(-1) @map("photo_item_id") - - @@map("support_tickets") -} - - -model UsersBadges { - id Int @id @default(autoincrement()) - userId Int @default(0) @map("user_id") - slotId Int @default(0) @map("slot_id") - badgeCode String @db.VarChar(32) @map("badge_code") - - @@map("users_badges") -} - - -model UsersSettings { - id Int @id @default(autoincrement()) - userId Int @unique @default(0) @map("user_id") - credits Int @default(0) - achievementScore Int @default(0) @map("achievement_score") - dailyRespectPoints Int @default(3) @map("daily_respect_points") - dailyPetRespectPoints Int @default(3) @map("daily_pet_respect_points") - respectsGiven Int @default(0) @map("respects_given") - respectsReceived Int @default(0) @map("respects_received") - guildId Int @default(0) @map("guild_id") - canChangeName String @default("0") @map("can_change_name") - canTrade String @default("1") @map("can_trade") - isCitizen String @default("0") @map("is_citizen") - citizenLevel Int @default(0) @map("citizen_level") - helperLevel Int @default(0) @map("helper_level") - tradelockAmount Int @default(0) @map("tradelock_amount") - cfhSend Int @default(0) @map("cfh_send") - cfhAbusive Int @default(0) @map("cfh_abusive") - cfhWarnings Int @default(0) @map("cfh_warnings") - cfhBans Int @default(0) @map("cfh_bans") - blockFollowing String @default("0") @map("block_following") - blockFriendrequests String @default("0") @map("block_friendrequests") - blockRoominvites String @default("0") @map("block_roominvites") - volumeSystem Int @default(100) @map("volume_system") - volumeFurni Int @default(100) @map("volume_furni") - volumeTrax Int @default(100) @map("volume_trax") - oldChat String @default("0") @map("old_chat") - blockCameraFollow String @default("0") @map("block_camera_follow") - chatColor Int @default(0) @map("chat_color") - homeRoom Int @default(0) @map("home_room") - onlineTime Int @default(0) @map("online_time") - tags String @default("Arcturus Emulator;") @db.VarChar(255) - clubExpireTimestamp Int @default(0) @map("club_expire_timestamp") - loginStreak Int @default(0) @map("login_streak") - rentSpaceId Int @default(0) @map("rent_space_id") - rentSpaceEndtime Int @default(0) @map("rent_space_endtime") - hofPoints Int @default(0) @map("hof_points") - blockAlerts String @default("0") @map("block_alerts") - talentTrackCitizenshipLevel Int @default(-1) @map("talent_track_citizenship_level") - talentTrackHelpersLevel Int @default(-1) @map("talent_track_helpers_level") - ignoreBots String @default("0") @map("ignore_bots") - ignorePets String @default("0") @map("ignore_pets") - nux String @default("0") - muteEndTimestamp Int @default(0) @map("mute_end_timestamp") - allowNameChange String @default("0") @map("allow_name_change") - perkTrade String @default("0") @map("perk_trade") - forumsPostCount Int? @default(0) @map("forums_post_count") - uiFlags Int @default(1) @map("ui_flags") - hasGottenDefaultSavedSearches Int @default(0) @map("has_gotten_default_saved_searches") - hcGiftsClaimed Int? @default(0) @map("hc_gifts_claimed") - lastHcPayday Int? @default(0) @map("last_hc_payday") - maxRooms Int? @default(50) @map("max_rooms") - maxFriends Int? @default(300) @map("max_friends") - - @@map("users_settings") -} - - -model WebsiteArticles { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - slug String @unique @db.VarChar(255) - title String @db.VarChar(255) - shortStory String @map("short_story") @db.VarChar(255) - fullStory String @map("full_story") @db.Text - userId Int? @map("user_id") - image String @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@index([userId]) - @@map("website_articles") -} - -model WebsiteLanguages { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - countryCode String @unique @map("country_code") @db.VarChar(8) - language String @unique @db.VarChar(255) - - @@map("website_languages") -} - -model UserReferrals { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - referralsTotal BigInt @map("referrals_total") @db.UnsignedBigInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("user_referrals") -} - -model Referrals { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - referredUserId BigInt @map("referred_user_id") @db.UnsignedBigInt - referredUserIp String @map("referred_user_ip") @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("referrals") -} - -model ClaimedReferralLogs { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - ipAddress String @map("ip_address") @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("claimed_referral_logs") -} - -model WebsiteHousekeepingPermissions { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - permission String @unique @db.VarChar(255) - minRank Int @map("min_rank") - description String? @db.VarChar(255) - groupName String? @map("group_name") @db.VarChar(100) - dependsOn String? @map("depends_on") @db.VarChar(255) - updatedBy Int? @map("updated_by") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_housekeeping_permissions") -} - -model WebsiteIpWhitelist { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - ipAddress String @map("ip_address") @db.VarChar(255) - asn String? @db.VarChar(255) - whitelistAsn Boolean @default(false) @map("whitelist_asn") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_ip_whitelist") -} - -model WebsiteIpBlacklist { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - ipAddress String @map("ip_address") @db.VarChar(255) - asn String? @db.VarChar(255) - blacklistAsn Boolean @default(false) @map("blacklist_asn") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_ip_blacklist") -} - -model WebsitePermissions { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - permission String @unique @db.VarChar(255) - minRank Int @map("min_rank") - description String? @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_permissions") -} - -model WebsiteArticleReactions { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") @db.UnsignedInt - articleId BigInt @map("article_id") @db.UnsignedBigInt - reaction String @db.VarChar(50) - active Boolean @default(true) - - @@index([articleId]) - @@index([userId]) - @@map("website_article_reactions") -} - -model WebsiteWordfilter { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - word String @unique @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_wordfilter") -} - -model WebsiteWriteableBoxes { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - title String @db.VarChar(255) - icon String? @db.VarChar(255) - content String @db.Text - position Int @default(0) - isActive Boolean @default(true) @map("is_active") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_writeable_boxes") -} - -model WebsiteLoginLogs { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - ip String @db.VarChar(45) - userAgent String? @map("user_agent") @db.VarChar(512) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - - @@index([userId]) - @@map("website_login_logs") -} - -model WebsiteBetaCodes { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - code String @unique @db.VarChar(255) - userId Int? @map("user_id") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_beta_codes") -} - -model WebsiteTeams { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - rankName String @unique @map("rank_name") @db.VarChar(255) - hiddenRank Boolean @default(false) @map("hidden_rank") - badge String? @db.VarChar(255) - jobDescription String? @map("job_description") @db.VarChar(255) - staffColor String @default("#327fa8") @map("staff_color") @db.VarChar(255) - staffBackground String @default("staff-bg.png") @map("staff_background") @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_teams") -} - -model WebsiteStaffApplications { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - rankId Int @map("rank_id") - content String @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_staff_applications") -} - -model WebsiteOpenPositions { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - permissionId Int @map("permission_id") - description String @db.VarChar(255) - applyFrom DateTime? @map("apply_from") @db.Timestamp(0) - applyTo DateTime? @map("apply_to") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_open_positions") -} - -model WebsiteArticleComments { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - articleId BigInt @map("article_id") @db.UnsignedBigInt - userId Int @map("user_id") - comment String @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@index([articleId]) - @@index([userId]) - @@map("website_article_comments") -} - -model WebsiteRareValueCategories { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @unique @db.VarChar(255) - badge String @db.VarChar(255) - priority Int @default(1) @db.UnsignedInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_rare_value_categories") -} - -model WebsiteRareValues { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - categoryId BigInt @map("category_id") @db.UnsignedBigInt - itemId Int? @map("item_id") - name String @db.VarChar(255) - creditValue String? @map("credit_value") @db.VarChar(255) - currencyValue String? @map("currency_value") @db.VarChar(255) - currencyType String @default("diamonds") @map("currency_type") @db.VarChar(255) - furnitureIcon String @map("furniture_icon") @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_rare_values") -} - -model WebsiteRuleCategories { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @unique @db.VarChar(255) - description String @db.VarChar(255) - badge String @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_rule_categories") -} - -model WebsiteRules { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - categoryId BigInt? @map("category_id") @db.UnsignedBigInt - paragraph String @db.Char(8) - rule String @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_rules") -} - -model WebsiteInstallation { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - step Int? @default(0) @db.UnsignedInt - completed Boolean @default(false) - installationKey String? @map("installation_key") @db.VarChar(255) - userIp String? @map("user_ip") @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_installation") -} - -model WebsiteHelpCenterCategories { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @unique @db.VarChar(255) - content String @db.Text - position Int @default(1) @db.UnsignedInt - imageUrl String? @map("image_url") @db.VarChar(255) - buttonText String? @map("button_text") @db.VarChar(255) - buttonUrl String? @map("button_url") @db.VarChar(255) - buttonColor String @default("#eeb425") @map("button_color") @db.Char(16) - buttonBorderColor String @default("#facc15") @map("button_border_color") @db.Char(16) - smallBox Boolean @default(false) @map("small_box") - - @@map("website_help_center_categories") -} - -model WebsiteHelpCenterTickets { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int? @map("user_id") - categoryId BigInt? @map("category_id") @db.UnsignedBigInt - title String @db.VarChar(255) - content String @db.Text - open Boolean @default(true) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@index([userId]) - @@index([categoryId]) - @@map("website_help_center_tickets") -} - -model WebsitePaypalTransactions { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - transactionId String @map("transaction_id") @db.VarChar(255) - status String? @db.VarChar(255) - description String? @map("description") @db.VarChar(512) - amount Float - currency String @default("USD") @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_paypal_transactions") -} - -model WebsiteShopArticles { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - categoryId BigInt? @map("website_shop_category_id") @db.UnsignedBigInt - name String @unique @db.VarChar(255) - info String @db.VarChar(255) - iconUrl String @map("icon_url") @db.VarChar(255) - color String @db.VarChar(255) - costs Int @db.UnsignedInt - giveRank Int? @map("give_rank") - isGiftable Boolean @default(false) @map("is_giftable") - credits Int? @db.UnsignedInt - duckets Int? @db.UnsignedInt - diamonds Int? @db.UnsignedInt - badges String? @db.VarChar(255) - furniture Json? - position Int @default(0) @db.UnsignedInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@index([categoryId]) - @@map("website_shop_articles") -} - -model WebsiteShopVouchers { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - code String @unique @db.VarChar(255) - maxUses Int @default(1) @map("max_uses") @db.UnsignedInt - useCount Int @default(0) @map("use_count") @db.UnsignedInt - amount Int @db.UnsignedInt - expiresAt DateTime? @map("expires_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_shop_vouchers") -} - -model WebsiteUsedShopVouchers { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - voucherId BigInt @map("voucher_id") @db.UnsignedBigInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_used_shop_vouchers") -} - -model WebsiteShopArticleFeatures { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - articleId BigInt @map("article_id") @db.UnsignedBigInt - content String @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_shop_article_features") -} - -model WebsiteHelpCenterTicketReplies { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - ticketId BigInt @map("ticket_id") @db.UnsignedBigInt - userId Int @map("user_id") - content String @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_help_center_ticket_replies") -} - -model WebsiteTicket { - id Int @id @default(autoincrement()) - subject String @db.VarChar(255) - category String @default("general") @db.VarChar(50) - priority String @default("normal") @db.VarChar(20) - status String @default("open") @db.VarChar(20) - creatorId Int @map("creator_id") - assigneeId Int? @map("assignee_id") - closedAt DateTime? @map("closed_at") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - creator User @relation("ticketCreator", fields: [creatorId], references: [id]) - assignee User? @relation("ticketAssignee", fields: [assigneeId], references: [id]) - messages WebsiteTicketMessage[] - - @@index([creatorId]) - @@index([assigneeId]) - @@index([status]) - @@map("website_tickets") -} - -model WebsiteTicketMessage { - id Int @id @default(autoincrement()) - ticketId Int @map("ticket_id") - userId Int @map("user_id") - message String @db.Text - isStaff Int @default(0) @map("is_staff") - createdAt DateTime @default(now()) @map("created_at") - - ticket WebsiteTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id]) - - @@index([ticketId]) - @@index([userId]) - @@map("website_ticket_messages") -} - -model WebsiteTicketTemplate { - id Int @id @default(autoincrement()) - title String @db.VarChar(255) - content String @db.Text - category String @default("general") @db.VarChar(50) - sortOrder Int @default(0) @map("sort_order") - createdAt DateTime @default(now()) @map("created_at") - - @@map("website_ticket_templates") -} - -model UserWatch { - id Int @id @default(autoincrement()) - staffId Int @map("staff_id") - targetUserId Int @map("target_user_id") - reason String @default("") @db.VarChar(255) - createdAt DateTime @default(now()) @map("created_at") - - @@unique([staffId, targetUserId]) - @@index([targetUserId]) - @@map("user_watch") -} - -model AdminAuditLog { - id Int @id @default(autoincrement()) - userId Int @map("user_id") - action String @default("") - target String @default("") - targetId Int? @map("target_id") - details String? @db.Text - before String? @db.Text - after String? @db.Text - diff String? @db.Text - ipAddress String? @map("ip_address") @db.VarChar(45) - createdAt String @default("") @map("created_at") - updatedAt String? @map("updated_at") - user User @relation(fields: [userId], references: [id]) - - @@index([userId]) - @@index([target, targetId]) - @@index([action]) - @@map("admin_audit_log") -} - -model AclRole { - id Int @id @default(autoincrement()) - slug String @unique - title String @default("") - description String? @db.Text - createdAt DateTime? @default(now()) @map("created_at") - updatedAt DateTime? @updatedAt @map("updated_at") - rolePermissions AclModelPermission[] - modelRoles AclModelRole[] - - @@map("acl_roles") -} - -model AclPermission { - id Int @id @default(autoincrement()) - slug String @unique - title String @default("") - createdAt DateTime? @default(now()) @map("created_at") - updatedAt DateTime? @updatedAt @map("updated_at") - modelPermissions AclModelPermission[] - - @@map("acl_permissions") -} - -model AclModelRole { - id Int @id @default(autoincrement()) - modelType String @default("User") @map("model_type") - modelId Int @map("model_id") - roleId Int @map("role_id") - role AclRole @relation(fields: [roleId], references: [id], onDelete: Cascade) - - @@index([roleId]) - @@map("acl_model_roles") -} - -model AclModelPermission { - id Int @id @default(autoincrement()) - modelType String @default("Role") @map("model_type") - modelId Int @map("model_id") - permissionId Int @map("permission_id") - role AclRole @relation(fields: [modelId], references: [id], onDelete: Cascade) - permission AclPermission @relation(fields: [permissionId], references: [id], onDelete: Cascade) - - @@index([modelId]) - @@index([permissionId]) - @@map("acl_model_permissions") -} - -model AlertLogs { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - type String @db.VarChar(255) - severity String @db.VarChar(255) - message String @db.Text - context Json? - sentViaEmail Boolean @default(false) @map("sent_via_email") - sentViaDiscord Boolean @default(false) @map("sent_via_discord") - isRead Boolean @default(false) @map("is_read") - readAt DateTime? @map("read_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("alert_logs") -} - -model WebsiteUserGuestbooks { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - profileId Int @map("profile_id") - userId Int @map("user_id") - message String @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_user_guestbooks") -} - -model WebsiteShopCategories { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @db.VarChar(255) - description String? @db.Text - icon String? @db.VarChar(255) - order Int @default(0) - - @@map("website_shop_categories") -} - -model WebsiteMaintenanceTasks { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int? @map("user_id") - task String @db.VarChar(255) - completed Boolean @default(false) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_maintenance_tasks") -} - -model Tags { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @db.VarChar(255) - backgroundColor String @map("background_color") @db.VarChar(10) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("tags") -} - -model Taggables { - tagId BigInt @map("tag_id") @db.UnsignedBigInt - taggableId BigInt @map("taggable_id") @db.UnsignedBigInt - taggableType String @map("taggable_type") @db.VarChar(255) - - @@unique([tagId, taggableId, taggableType]) - @@map("taggables") -} - -model WebsiteBadges { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - badgeKey String @unique @map("badge_key") @db.VarChar(255) - badgeName String @map("badge_name") @db.VarChar(255) - badgeDescription String @map("badge_description") @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_badges") -} - -model WebsiteAds { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - image String @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_ads") -} - -model WebsiteDrawbadges { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - badgePath String @map("badge_path") @db.VarChar(255) - badgeUrl String @map("badge_url") @db.VarChar(255) - badgeName String @map("badge_name") @db.VarChar(255) - badgeDesc String @map("badge_desc") @db.VarChar(255) - published Boolean @default(false) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("website_drawbadges") -} - -model RadioRanks { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @db.VarChar(255) - description String? @db.VarChar(255) - badgeCode String? @map("badge_code") @db.VarChar(255) - isActive Boolean @default(true) @map("is_active") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_ranks") -} - -model RadioShouts { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - message String @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_shouts") -} - -model RadioApplications { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - realName String @map("real_name") @db.VarChar(255) - age Int - availability String @db.Text - experience String? @db.Text - motivation String @db.Text - musicStyle String? @map("music_style") @db.Text - discordUsername String? @map("discord_username") @db.VarChar(255) - rankId BigInt? @map("rank_id") @db.UnsignedBigInt - status String @default("pending") @db.VarChar(255) - approvedBy BigInt? @map("approved_by") @db.UnsignedBigInt - approvedAt DateTime? @map("approved_at") @db.Timestamp(0) - rejectedBy BigInt? @map("rejected_by") @db.UnsignedBigInt - rejectedAt DateTime? @map("rejected_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_applications") -} - -model RadioSchedules { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - day String @db.VarChar(255) - startTime DateTime @map("start_time") @db.Time - endTime DateTime @map("end_time") @db.Time - showName String? @map("show_name") @db.VarChar(255) - description String? @db.Text - isActive Boolean @default(true) @map("is_active") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_schedules") -} - -model RadioBanners { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - imagePath String @map("image_path") @db.VarChar(255) - title String? @db.VarChar(255) - description String? @db.Text - sortOrder Int @default(0) @map("sort_order") - isActive Boolean @default(true) @map("is_active") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_banners") -} - -model RadioHistory { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - showName String? @map("show_name") @db.VarChar(255) - startedAt DateTime @map("started_at") @db.Timestamp(0) - endedAt DateTime? @map("ended_at") @db.Timestamp(0) - listenersCount Int @default(0) @map("listeners_count") - notes String? @db.Text - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_history") -} - -model RadioContests { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - title String @db.VarChar(255) - description String? @db.Text - prize String? @db.VarChar(255) - startDate DateTime? @map("start_date") @db.Timestamp(0) - endDate DateTime? @map("end_date") @db.Timestamp(0) - winnerId BigInt? @map("winner_id") @db.UnsignedBigInt - isActive Boolean @default(true) @map("is_active") - createdBy BigInt? @map("created_by") @db.UnsignedBigInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_contests") -} - -model RadioGiveaways { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - title String @db.VarChar(255) - description String? @db.Text - prize String? @db.VarChar(255) - prizeAmount Int @default(0) @map("prize_amount") - prizeCurrency String? @map("prize_currency") @db.VarChar(50) - startDate DateTime? @map("start_date") @db.Timestamp(0) - endDate DateTime? @map("end_date") @db.Timestamp(0) - winnerId BigInt? @map("winner_id") @db.UnsignedBigInt - isActive Boolean @default(true) @map("is_active") - createdBy BigInt? @map("created_by") @db.UnsignedBigInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_giveaways") -} - -model RadioListenerPoints { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") - points Int @default(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_listener_points") -} - -model RadioSongRequests { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt? @map("user_id") @db.UnsignedBigInt - songTitle String? @map("song_title") @db.VarChar(255) - artist String? @map("artist") @db.VarChar(255) - isApproved Boolean @default(false) @map("is_approved") - isPlayed Boolean @default(false) @map("is_played") - votes Int @default(0) - submittedAt DateTime? @map("submitted_at") @db.Timestamp(0) - playedAt DateTime? @map("played_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@index([userId]) - @@map("radio_song_requests") -} - -model RadioSongVotes { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_song_votes") -} - -model GameScores { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId Int @map("user_id") @db.UnsignedInt - game String @db.VarChar(50) - score Int @default(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("game_scores") -} - -model GameRooms { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - game String @db.VarChar(50) - player1Id Int @map("player1_id") @db.UnsignedInt - player2Id Int? @map("player2_id") @db.UnsignedInt - state Json? - turn Int @default(1) @db.UnsignedInt - winnerId Int? @map("winner_id") @db.UnsignedInt - status String @default("waiting") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("game_rooms") -} - -model GameRewardClaims { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - game String @db.VarChar(50) - winsRequired Int @map("wins_required") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@unique([userId, game, winsRequired]) - @@map("game_reward_claims") -} - -model GameChatMessages { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - roomId BigInt @map("room_id") @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - message String @db.Text - type String @default("chat") - reactions Json? - createdAt DateTime @map("created_at") @db.Timestamp(0) - - @@map("game_chat_messages") -} - -model GameReplays { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - game String - player1Id BigInt @map("player1_id") @db.UnsignedBigInt - player2Id BigInt? @map("player2_id") @db.UnsignedBigInt - gameData Json @map("game_data") - moves Json - winner String? - player1Score Int @default(0) @map("player1_score") - player2Score Int @default(0) @map("player2_score") - startedAt DateTime @map("started_at") @db.Timestamp(0) - endedAt DateTime? @map("ended_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("game_replays") -} - -model GameRanks { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String - slug String @unique - icon String @default("⭐") - color String @default("#ffffff") - minPoints Int @default(0) @map("min_points") - maxPoints Int? @map("max_points") - tier Int @default(1) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("game_ranks") -} - -model UserGameRanks { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - game String - rankId BigInt @map("rank_id") @db.UnsignedBigInt - points Int @default(0) - wins Int @default(0) - gamesPlayed Int @default(0) @map("games_played") - winStreak Int @default(0) @map("win_streak") - bestStreak Int @default(0) @map("best_streak") - lastPlayedAt DateTime? @map("last_played_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@unique([userId, game]) - @@map("user_game_ranks") -} - -model GameInvitations { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - fromUserId BigInt @map("from_user_id") @db.UnsignedBigInt - toUserId BigInt @map("to_user_id") @db.UnsignedBigInt - game String @db.VarChar(255) - status String @default("pending") @db.VarChar(255) - betAmount Int @default(0) @map("bet_amount") - betCurrency String @default("credits") @map("bet_currency") @db.VarChar(255) - roomId BigInt? @map("room_id") @db.UnsignedBigInt - expiresAt DateTime? @map("expires_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("game_invitations") -} - -model UserGameXp { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - game String @db.VarChar(255) - xp Int @default(0) - level Int @default(1) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@unique([userId, game]) - @@map("user_game_xp") -} - -model GameLevelRewards { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - game String @db.VarChar(255) - level Int - rewardType String @map("reward_type") @db.VarChar(255) - rewardCurrency String? @map("reward_currency") @db.VarChar(255) - rewardAmount Int @default(0) @map("reward_amount") - claimed Boolean @default(false) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("game_level_rewards") -} - -model DailyChallenges { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @db.VarChar(255) - description String @db.VarChar(255) - game String? @db.VarChar(255) - type String @db.VarChar(255) - target Int - rewardCurrency String @default("credits") @map("reward_currency") @db.VarChar(255) - rewardAmount Int @map("reward_amount") - date DateTime @db.Date - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("daily_challenges") -} - -model UserChallengeProgress { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - challengeId BigInt @map("challenge_id") @db.UnsignedBigInt - progress Int @default(0) - completed Boolean @default(false) - claimed Boolean @default(false) - completedAt DateTime? @map("completed_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@unique([userId, challengeId]) - @@map("user_challenge_progress") -} - -model PersonalAccessTokens { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - tokenableId BigInt @map("tokenable_id") @db.UnsignedBigInt - tokenableType String @map("tokenable_type") @db.VarChar(255) - name String @db.Text - token String @unique @db.VarChar(64) - abilities String? @db.Text - lastUsedAt DateTime? @map("last_used_at") @db.Timestamp(0) - expiresAt DateTime? @map("expires_at") @db.Timestamp(0) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("personal_access_tokens") -} - -model StaffActivities { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - action String @db.VarChar(50) - targetType String? @map("target_type") @db.VarChar(100) - targetId BigInt? @map("target_id") @db.UnsignedBigInt - description String @db.Text - ipAddress String? @map("ip_address") @db.VarChar(45) - metadata Json? @db.Json - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@index([userId]) - @@index([action]) - @@map("staff_activities") -} - -model EmailTemplates { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @unique @db.VarChar(255) - subject String @db.VarChar(255) - body String @db.Text - variables String? @db.Text - isActive Boolean @default(true) @map("is_active") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("email_templates") -} - -model SocialAccounts { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - userId BigInt @map("user_id") @db.UnsignedBigInt - provider String @db.VarChar(255) - providerId String @map("provider_id") @db.VarChar(255) - avatar String? @db.VarChar(255) - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@unique([provider, providerId]) - @@map("social_accounts") -} - -model RadioApiKeys { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - name String @db.VarChar(255) - key String @unique @db.VarChar(64) - allowedIps String? @map("allowed_ips") @db.VarChar(255) - permissions Json? @db.Json - rateLimit Int @default(300) @map("rate_limit") - lastUsedAt DateTime? @map("last_used_at") @db.Timestamp(0) - expiresAt DateTime? @map("expires_at") @db.Timestamp(0) - isActive Boolean @default(true) @map("is_active") - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_api_keys") -} - -model RadioSongPlays { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - title String @db.VarChar(255) - artist String? @db.VarChar(255) - album String? @db.VarChar(255) - artworkUrl String? @map("artwork_url") @db.VarChar(255) - duration Int? @db.UnsignedInt - playedAt DateTime @map("played_at") @db.Timestamp(0) - djId BigInt? @map("dj_id") @db.UnsignedBigInt - metadata Json? @db.Json - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_song_plays") -} - -model RadioAutoDjPlaylist { - id BigInt @id @default(autoincrement()) @db.UnsignedBigInt - title String @db.VarChar(255) - artist String? @db.VarChar(255) - album String? @db.VarChar(255) - artworkUrl String? @map("artwork_url") @db.VarChar(255) - duration Int? @db.UnsignedInt - playCount Int @default(0) @map("play_count") @db.UnsignedInt - lastPlayedAt DateTime? @map("last_played_at") @db.Timestamp(0) - isActive Boolean @default(true) @map("is_active") - sortOrder Int @default(0) @map("sort_order") @db.UnsignedInt - createdAt DateTime? @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @map("updated_at") @db.Timestamp(0) - - @@map("radio_auto_dj_playlist") -} - -model WebsiteBanner { - id Int @id @default(autoincrement()) - title String @default("") - subtitle String @default("") - image String @default("") @db.VarChar(500) - link String @default("") @db.VarChar(500) - color String @default("") @db.VarChar(20) - isActive Int @default(1) @map("is_active") - sortOrder Int @default(0) @map("sort_order") - startDate String? @map("start_date") @db.VarChar(50) - endDate String? @map("end_date") @db.VarChar(50) - - @@map("website_banners") -} - -model WebsiteEventType { - id Int @id @default(autoincrement()) - name String @db.VarChar(100) - slug String @unique @db.VarChar(100) - description String @default("") @db.VarChar(500) - color String @default("#3b82f6") @db.VarChar(20) - icon String @default("calendar") @db.VarChar(50) - isActive Int @default(1) @map("is_active") - minRank Int @default(0) @map("min_rank") - events WebsiteEvent[] - - @@map("website_event_types") -} - -model WebsiteEvent { - id Int @id @default(autoincrement()) - title String @db.VarChar(255) - description String @db.Text - typeId Int @map("type_id") - hostUserId Int @map("host_user_id") - roomId Int? @map("room_id") - startsAt DateTime @map("starts_at") - endsAt DateTime? @map("ends_at") - maxPlayers Int? @map("max_players") - isRecurring Int @default(0) @map("is_recurring") - recurrenceRule String? @map("recurrence_rule") @db.VarChar(255) - status String @default("draft") @db.VarChar(20) - image String? @db.VarChar(500) - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - type WebsiteEventType @relation(fields: [typeId], references: [id]) - registrations WebsiteEventRegistration[] - prizes WebsiteEventPrize[] - winners WebsiteEventWinner[] - - @@index([typeId]) - @@index([hostUserId]) - @@index([startsAt]) - @@index([status]) - @@map("website_events") -} - -model WebsiteEventRegistration { - id Int @id @default(autoincrement()) - eventId Int @map("event_id") - userId Int @map("user_id") - createdAt DateTime @default(now()) @map("created_at") - - event WebsiteEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) - - @@unique([eventId, userId]) - @@index([userId]) - @@map("website_event_registrations") -} - -model WebsiteEventPrize { - id Int @id @default(autoincrement()) - eventId Int @map("event_id") - position Int @default(1) - prizeType String @default("badge") @map("prize_type") @db.VarChar(20) - badgeCode String? @map("badge_code") @db.VarChar(50) - credits Int @default(0) - pixels Int @default(0) - points Int @default(0) - itemId Int? @map("item_id") - description String @default("") @db.VarChar(255) - - event WebsiteEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) - - @@index([eventId]) - @@map("website_event_prizes") -} - -model WebsiteEventWinner { - id Int @id @default(autoincrement()) - eventId Int @map("event_id") - userId Int @map("user_id") - position Int @default(1) - createdAt DateTime @default(now()) @map("created_at") - - event WebsiteEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) - - @@unique([eventId, userId]) - @@index([userId]) - @@map("website_event_winners") -} - -model WebsitePoll { - id Int @id @default(autoincrement()) - title String @db.VarChar(255) - description String? @db.Text - status String @default("draft") @db.VarChar(20) - showResults Int @default(1) @map("show_results") - multipleChoice Int @default(0) @map("multiple_choice") - startsAt DateTime? @map("starts_at") - endsAt DateTime? @map("ends_at") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - - questions WebsitePollQuestion[] - - @@index([status]) - @@map("website_polls") -} - -model WebsitePollQuestion { - id Int @id @default(autoincrement()) - pollId Int @map("poll_id") - question String @db.VarChar(500) - type String @default("single") @db.VarChar(20) - sortOrder Int @default(0) @map("sort_order") - options String @db.Text - - poll WebsitePoll @relation(fields: [pollId], references: [id], onDelete: Cascade) - votes WebsitePollVote[] - - @@index([pollId]) - @@map("website_poll_questions") -} - -model WebsitePollVote { - id Int @id @default(autoincrement()) - questionId Int @map("question_id") - userId Int @map("user_id") - answer String @db.VarChar(500) - createdAt DateTime @default(now()) @map("created_at") - - question WebsitePollQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade) - - @@unique([questionId, userId]) - @@index([userId]) - @@map("website_poll_votes") -} -model AchievementsTalents { - id Int @id @default(autoincrement()) - type String @db.VarChar(255) @default("citizenship") - level Int @default(0) - achievementIds String @map("achievement_ids") @db.VarChar(100) @default("") - achievementLevels String @map("achievement_levels") @db.VarChar(100) @default("") - rewardFurni String @map("reward_furni") @db.VarChar(100) @default("") - rewardPerks String @map("reward_perks") @db.VarChar(100) @default("") - rewardBadges String @map("reward_badges") @db.VarChar(100) @default("") - - @@map("achievements_talents") -} - -model Bots { - id Int @id @default(autoincrement()) - userId Int @map("user_id") @default(0) - roomId Int @map("room_id") @default(0) - name String @db.VarChar(25) @default("") - motto String @db.VarChar(100) @default("") - figure String @db.VarChar(500) @default("") - gender String @db.VarChar(255) @default("M") - x Int @default(0) - y Int @default(0) - z Float @db.Double @default(0.0) - rot Int @default(0) - chatLines String @map("chat_lines") @db.VarChar(5112) @default("") - chatAuto String @map("chat_auto") @db.VarChar(255) @default("1") - chatRandom String @map("chat_random") @db.VarChar(255) @default("1") - chatDelay Int @map("chat_delay") @default(5) - dance Int @default(0) - freeroam String @db.VarChar(255) @default("0") - type String @db.VarChar(255) @default("generic") - effect Int @default(0) - bubbleId Int? @map("bubble_id") @default(31) - - @@map("bots") -} - -model CatalogClothing { - id Int @id @default(autoincrement()) - name String @db.VarChar(75) - setid String @db.VarChar(75) - - @@map("catalog_clothing") -} - -model CatalogClubOffers { - id Int @id - enabled String @db.VarChar(255) @default("1") - name String @db.VarChar(35) - days Int - credits Int @default(10) - points Int @default(0) - pointsType Int @map("points_type") @default(0) - type String @db.VarChar(255) @default("HC") - deal String @db.VarChar(255) @default("0") - giftable String @db.VarChar(255) @default("0") - - @@map("catalog_club_offers") -} - -model CatalogFeaturedPages { - slotId Int @id @map("slot_id") - image String @db.VarChar(70) @default("") - caption String @db.VarChar(130) @default("") - type String @db.VarChar(255) @default("page_name") - expireTimestamp Int @map("expire_timestamp") @default(-1) - pageName String @map("page_name") @db.VarChar(30) @default("") - pageId Int @map("page_id") @default(0) - productName String @map("product_name") @db.VarChar(40) @default("") - - @@map("catalog_featured_pages") -} - -model CatalogItemsLimited { - catalogItemId Int @map("catalog_item_id") - number Int - userId Int @map("user_id") @default(0) - timestamp Int @default(0) - itemId Int @map("item_id") @default(0) - - @@unique([catalogItemId, number], map: "catalog_item_id") - @@map("catalog_items_limited") -} - -model CatalogTargetOffers { - id Int @id @default(autoincrement()) - offerCode String @map("offer_code") @db.VarChar(32) - title String @db.VarChar(128) @default("") - description String @db.VarChar(2048) @default("") - image String @db.VarChar(128) - icon String @db.VarChar(128) - endTimestamp Int @map("end_timestamp") - credits Int @default(10) - points Int @default(10) - pointsType Int @map("points_type") @default(5) - purchaseLimit Int @map("purchase_limit") @default(5) - catalogItem Int @map("catalog_item") - vars String @db.VarChar(1024) @default("") - - @@map("catalog_target_offers") -} - -model ChatlogsPrivate { - id Int @id @default(autoincrement()) - userFromId Int @map("user_from_id") - userToId Int @map("user_to_id") - message String @db.VarChar(255) - timestamp Int @default(0) - - @@map("chatlogs_private") -} - -model CraftingAltarsRecipes { - altarId Int @map("altar_id") - recipeId Int @map("recipe_id") - - @@unique([altarId, recipeId], map: "altar_id") - @@map("crafting_altars_recipes") -} - -model CraftingRecipes { - id Int @id - productName String @map("product_name") @db.VarChar(64) - reward Int - enabled String @db.VarChar(1) @default("1") - achievement String @db.VarChar(255) @default("") - secret String @db.VarChar(1) @default("0") - limited String @db.VarChar(1) @default("0") - remaining Int @default(0) - - @@unique([productName], map: "name") - @@map("crafting_recipes") -} - -model GiftWrappers { - id Int @id @default(autoincrement()) - spriteId Int @map("sprite_id") - itemId Int @map("item_id") - type String @default("wrapper") - - @@map("gift_wrappers") -} - -model GroupsItems { - type String @map("type") - id Int @map("id") - firstvalue String @db.VarChar(255) - secondvalue String @db.VarChar(2000) - enabled String @default("1") - - @@id([id, type]) - @@map("groups_items") -} - -model GuildForumViews { - id Int @id @default(autoincrement()) - userId Int @map("user_id") - guildId Int @map("guild_id") - timestamp Int - - @@map("guild_forum_views") -} - -model GuildsElements { - id Int @map("id") - firstvalue String @db.VarChar(300) - secondvalue String @db.VarChar(300) - type String @db.VarChar(50) - enabled String @default("1") - - @@unique([id, type], map: "id") - @@map("guilds_elements") -} - -model HotelviewNews { - id Int @id @default(autoincrement()) - title String @db.VarChar(100) - text String @db.VarChar(500) - buttonText String @db.VarChar(50) @map("button_text") - buttonType String @default("web") @map("button_type") - buttonLink String @db.VarChar(200) @map("button_link") - image String @db.VarChar(200) - - @@map("hotelview_news") -} - -model ItemsCrackable { - itemId Int @id @map("item_id") - itemName String @db.VarChar(255) @map("item_name") - count Int - prizes String @db.VarChar(255) @default("179:1") - achievementTick String @db.VarChar(64) @map("achievement_tick") - achievementCracked String @db.VarChar(64) @map("achievement_cracked") - requiredEffect Int @default(0) @map("required_effect") - subscriptionDuration Int? @map("subscription_duration") - subscriptionType String? @db.VarChar(255) @map("subscription_type") - - @@map("items_crackable") -} - -model ItemsHighscoreData { - id Int @id @default(autoincrement()) - itemId Int @map("item_id") - userIds String @db.VarChar(500) @map("user_ids") - score Int - isWin Int? @default(0) @map("is_win") - timestamp Int - - @@map("items_highscore_data") -} - -model LogsHcPayday { - id Int @id @default(autoincrement()) - timestamp Int? - userId Int? @map("user_id") - hcStreak Int? @map("hc_streak") - totalCoinsSpent Int? @map("total_coins_spent") - rewardCoinsSpent Int? @map("reward_coins_spent") - rewardStreak Int? @map("reward_streak") - totalPayout Int? @map("total_payout") - currency String? @db.VarChar(255) - claimed Int? @default(0) - - @@map("logs_hc_payday") -} - -model MessengerCategories { - id Int @id @default(autoincrement()) - name String @db.VarChar(25) - userId Int @map("user_id") - - @@map("messenger_categories") -} - -model NavigatorFilter { - key String @id @db.VarChar(11) - field String @db.VarChar(32) - compare String - databaseQuery String @db.VarChar(1024) @map("database_query") - - @@map("navigator_filter") -} - -model NuxGifts { - id Int @id @default(autoincrement()) - type String @default("item") - value String @db.VarChar(32) - image String @db.VarChar(256) - - @@map("nux_gifts") -} - -model OldGuildsForums { - id Int @id @default(autoincrement()) - guildId Int @map("guild_id") - userId Int @map("user_id") - subject String @db.MediumText - message String @db.LongText - state String @default("OPEN") - adminId Int @default(0) @map("admin_id") - pinned String @default("0") - locked String @default("0") - timestamp Int - - @@map("old_guilds_forums") -} - -model OldGuildsForumsComments { - id Int @id @default(autoincrement()) - threadId Int @map("thread_id") - userId Int @map("user_id") - timestamp Int - message String @db.LongText - state String @default("OPEN") - adminId Int @default(0) @map("admin_id") - - @@map("old_guilds_forums_comments") -} - -model PetActions { - id Int @id @default(autoincrement()) @map("ID") - petType Int @map("pet_type") - petName String @db.VarChar(32) @map("pet_name") - offspringType Int @default(-1) @map("offspring_type") - happyActions String @default("") @db.VarChar(100) @map("happy_actions") - tiredActions String @default("") @db.VarChar(100) @map("tired_actions") - randomActions String @default("") @db.VarChar(100) @map("random_actions") - canSwim String? @default("0") @map("can_swim") - - @@map("pet_actions") -} - -model PetBreeds { - race Int @map("race") - colorOne Int @map("color_one") - colorTwo Int @map("color_two") - hasColorOne String @default("0") @map("has_color_one") - hasColorTwo String @default("0") @map("has_color_two") - - @@unique([race, colorOne, colorTwo, hasColorOne, hasColorTwo], map: "idx_name") - @@map("pet_breeds") -} - -model PetCommandsData { - commandId Int @id @map("command_id") - text String @db.VarChar(15) - requiredLevel Int @map("required_level") - rewardXp Int @default(5) @map("reward_xp") - costHappyness Int @default(0) @map("cost_happyness") - costEnergy Int @default(0) @map("cost_energy") - - @@map("pet_commands_data") -} - -model Polls { - id Int @id @default(autoincrement()) - title String @default("Hey! We'd appreciate it if you could take some time to answer these questions. It will help improve our hotel.") @db.VarChar(255) - thanksMessage String @map("thanks_message") @db.VarChar(255) - rewardBadge String @default("") @map("reward_badge") @db.VarChar(10) - - @@map("polls") -} - -model PollsAnswers { - pollId Int @map("poll_id") - userId Int @map("user_id") - questionId Int @map("question_id") - answer String @db.VarChar(255) - - @@unique([pollId, userId, questionId], map: "unique_index") - @@map("polls_answers") -} - -model PollsQuestions { - id Int @id @default(autoincrement()) - parentId Int @default(0) @map("parent_id") - pollId Int @map("poll_id") - order Int - question String @db.VarChar(255) - type Int @default(2) - minSelections Int @default(1) @map("min_selections") - options String @db.VarChar(255) - - @@map("polls_questions") -} - -model RoomModels { - name String @id @db.VarChar(100) - doorX Int @map("door_x") - doorY Int @map("door_y") - doorDir Int @default(2) @map("door_dir") - heightmap String @db.Text - publicItems String @map("public_items") @db.Text - clubOnly String @default("0") @map("club_only") - - @@map("room_models") -} - -model RoomTradeLogItems { - id Int @map("id") - itemId Int @map("item_id") - userId Int @map("user_id") - - @@unique([id, itemId, userId], map: "id") - @@map("room_trade_log_items") -} - -model RoomWordfilter { - roomId Int @map("room_id") - word String @db.VarChar(25) - - @@unique([roomId, word], map: "unique_index") - @@map("room_wordfilter") -} - -model SanctionLevels { - level Int @id - type String - hourLength Int @map("hour_length") - probationDays Int @map("probation_days") - - @@map("sanction_levels") -} - -model Sanctions { - id Int @id @default(autoincrement()) - habboId Int @default(0) @map("habbo_id") - sanctionLevel Int @default(0) @map("sanction_level") - probationTimestamp Int @default(0) @map("probation_timestamp") - reason String @db.VarChar(255) @default("") @map("reason") - tradeLockedUntil Int @default(0) @map("trade_locked_until") - isMuted Int @default(0) @map("is_muted") - muteDuration Int @default(0) @map("mute_duration") - - @@map("sanctions") -} - -model SupportCfhCategories { - id Int @id @default(autoincrement()) - nameInternal String? @db.VarChar(255) @map("name_internal") - nameExternal String? @db.VarChar(255) @map("name_external") - - @@map("support_cfh_categories") -} - -model SupportCfhTopics { - id Int @id @default(autoincrement()) - categoryId Int? @map("category_id") - nameInternal String? @db.VarChar(255) @map("name_internal") - nameExternal String? @db.VarChar(255) @map("name_external") - action String? @default("mods") - ignoreTarget String @default("0") @map("ignore_target") - autoReply String? @db.Text @map("auto_reply") - defaultSanction Int @default(0) @map("default_sanction") - - @@map("support_cfh_topics") -} - -model SupportIssueCategories { - id Int @id @default(autoincrement()) - name String @db.VarChar(255) @default("PII") - - @@map("support_issue_categories") -} - -model SupportIssuePresets { - id Int @id @default(autoincrement()) - category Int @default(1) - name String @db.VarChar(100) @default("") @map("name") - message String @db.VarChar(300) @default("") - reminder String @db.VarChar(100) - banFor Int @default(0) @map("ban_for") - muteFor Int @default(0) @map("mute_for") - - @@map("support_issue_presets") -} - -model SupportPresets { - id Int @id @default(autoincrement()) - type String @default("user") - preset String @db.VarChar(200) - - @@map("support_presets") -} - -model UsersAchievements { - userId Int @map("user_id") - achievementName String @db.VarChar(255) @map("achievement_name") - progress Int @default(1) - - @@unique([userId, achievementName], map: "user_id") - @@map("users_achievements") -} - -model UsersAchievementsQueue { - userId Int @map("user_id") - achievementId Int @map("achievement_id") - amount Int - - @@unique([userId, achievementId], map: "unique_index") - @@map("users_achievements_queue") -} - -model UsersClothing { - userId Int @map("user_id") - clothingId Int @map("clothing_id") - - @@unique([userId, clothingId], map: "user_id") - @@map("users_clothing") -} - -model UsersEffects { - userId Int @map("user_id") - effect Int - duration Int @default(86400) - activationTimestamp Int @default(-1) @map("activation_timestamp") - total Int @default(1) - - @@unique([userId, effect], map: "user_id") - @@map("users_effects") -} - -model UsersFavoriteRooms { - userId Int @map("user_id") - roomId Int @map("room_id") - - @@unique([userId, roomId], map: "user_id") - @@map("users_favorite_rooms") -} - -model UsersPets { - id Int @id @default(autoincrement()) - userId Int @map("user_id") - roomId Int @map("room_id") - name String @default("User Pet") @db.VarChar(15) - race Int - type Int - color String @db.VarChar(6) - happyness Int @default(100) - experience Int @default(0) - energy Int @default(100) - hunger Int @default(0) - thirst Int @default(0) - respect Int @default(0) - created Int - x Int @default(0) - y Int @default(0) - z Float @default(0) - rot Int @default(0) - hairStyle Int @default(-1) @map("hair_style") - hairColor Int @default(0) @map("hair_color") - saddle String @default("0") - ride String @default("0") - mpType Int @default(0) @map("mp_type") - mpColor Int @default(0) @map("mp_color") - mpNose Int @default(0) @map("mp_nose") - mpNoseColor Int @default(0) @map("mp_nose_color") - mpEyes Int @default(0) @map("mp_eyes") - mpEyesColor Int @default(0) @map("mp_eyes_color") - mpMouth Int @default(0) @map("mp_mouth") - mpMouthColor Int @default(0) @map("mp_mouth_color") - mpDeathTimestamp Int @default(0) @map("mp_death_timestamp") - mpBreedable String @default("0") @map("mp_breedable") - mpAllowBreed String @default("0") @map("mp_allow_breed") - gnomeData String @default("") @db.VarChar(80) @map("gnome_data") - mpIsDead Int @default(0) @map("mp_is_dead") - saddleItemId Int? @map("saddle_item_id") - - @@map("users_pets") -} - -model UsersRecipes { - userId Int @map("user_id") - recipe Int - - @@unique([userId, recipe], map: "user_id") - @@map("users_recipes") -} - -model UsersSavedSearches { - id Int @id @default(autoincrement()) - searchCode String @db.VarChar(255) @map("search_code") - filter String? @db.VarChar(255) - userId Int @map("user_id") - - @@map("users_saved_searches") -} - -model UsersSubscriptions { - id Int @id @default(autoincrement()) @db.UnsignedInt - userId Int? @db.UnsignedInt @map("user_id") - subscriptionType String? @db.VarChar(255) @map("subscription_type") - timestampStart Int? @db.UnsignedInt @map("timestamp_start") - duration Int? @db.UnsignedInt - active Int? @default(1) - - @@map("users_subscriptions") -} - -model UsersTargetOfferPurchases { - userId Int @map("user_id") - offerId Int @map("offer_id") - state Int @default(0) - amount Int @default(0) - lastPurchase Int @default(0) @map("last_purchase") - - @@unique([userId, offerId], map: "use_id") - @@map("users_target_offer_purchases") -} - -model UsersWardrobe { - id Int @id @default(autoincrement()) - userId Int @default(0) @map("user_id") - slotId Int @default(0) @map("slot_id") - look String @db.VarChar(256) - gender String @default("F") - - @@map("users_wardrobe") -} - -model VoucherHistory { - id Int @id @default(autoincrement()) - voucherId Int @map("voucher_id") - userId Int @map("user_id") - timestamp Int - - @@map("voucher_history") -} - -model Vouchers { - id Int @id @default(autoincrement()) - code String @db.VarChar(10) - credits Int @default(0) - points Int @default(0) - pointsType Int @default(0) @map("points_type") - catalogItemId Int @default(0) @map("catalog_item_id") - amount Int @default(1) - limit Int @default(-1) - - @@map("vouchers") -} - -model YoutubePlaylists { - itemId Int @map("item_id") - playlistId String @db.VarChar(255) @map("playlist_id") - order Int - - @@unique([itemId, playlistId, order], map: "item_id") - @@map("youtube_playlists") -} diff --git a/scripts/apply-migrations.ts b/scripts/apply-migrations.ts index 8f385b63..dbd8c4ec 100644 --- a/scripts/apply-migrations.ts +++ b/scripts/apply-migrations.ts @@ -6,7 +6,7 @@ import { mysqlConnectionUrl } from "./db-url"; import { splitSqlStatements } from "./sql-statements"; const __dirname = dirname(fileURLToPath(import.meta.url)); -const MIGRATIONS_DIR = resolve(__dirname, "../prisma/migrations"); +const MIGRATIONS_DIR = resolve(__dirname, "../drizzle/migrations"); const TRACKING_TABLE = "cms_migrations"; interface MigrationFile { diff --git a/scripts/generate-drizzle-schema.mjs b/scripts/generate-drizzle-schema.mjs index 884a7544..62fbeacd 100644 --- a/scripts/generate-drizzle-schema.mjs +++ b/scripts/generate-drizzle-schema.mjs @@ -1,10 +1,11 @@ #!/usr/bin/env node // Generates src/db/schema.ts from: -// 1. prisma/schema.prisma -> TS field names (camelCase) + @map column names + table names -// 2. live MySQL introspection -> real column types (DB is authoritative for DDL) +// 1. existing src/db/schema.ts -> TS export names, camelCase fields, column maps, keys +// 2. live MySQL introspection -> real column types (DB is authoritative for DDL) // // The DB is owned by the Arcturus emulator; we never run drizzle-kit migrate/push. -// This schema is only used for the query builder + TypeScript types. +// CMS DDL lands in drizzle/migrations/*.sql via `pnpm db:migrate`. +// drizzle-kit (`pnpm db:generate` / studio / introspect) is draft/browse tooling only. // // Usage: pnpm db:schema:generate import "dotenv/config"; @@ -27,84 +28,124 @@ function mysqlConnectionUrl(value) { const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, ".."); -const SCHEMA_PRISMA = resolve(ROOT, "prisma/schema.prisma"); -const OUT = resolve(ROOT, "src/db/schema.ts"); +const SCHEMA_TS = resolve(ROOT, "src/db/schema.ts"); +const OUT = SCHEMA_TS; -const prismaSource = readFileSync(SCHEMA_PRISMA, "utf-8"); +// ---------- Parse existing Drizzle schema (naming source of truth) ---------- +const schemaSource = readFileSync(SCHEMA_TS, "utf-8"); -// ---------- Parse prisma/schema.prisma ---------- -const modelBlocks = [ - ...prismaSource.matchAll(/^model\s+(\w+)\s*\{([\s\S]*?)^\}/gm), -]; -const modelNames = new Set(modelBlocks.map((m) => m[1])); +/** + * @returns {{ name: string, table: string, fields: object[], ids: string[]|null, uniques: string[][] }} + */ +function parseDrizzleTables(source) { + const models = []; + const re2 = + /^export const (\w+) = mysqlTable\(\s*"([^"]+)"\s*,\s*\{([\s\S]*?)\n\}(?:,\s*\(t\)\s*=>\s*\[([\s\S]*?)\])?\s*\);/gm; -/** parse a model block -> { name, table, fields, ids, uniques, maps } */ -function parseModel(block) { - const [_full, name, body] = block; - const table = - body.match(/@@map\(\s*"([^"]+)"\s*\)/)?.[1] ?? name.toLowerCase(); + let match; + const seen = new Set(); + while ((match = re2.exec(source)) !== null) { + const [, name, table, body, extras] = match; + if (seen.has(name)) continue; + seen.add(name); + models.push(parseTableBody(name, table, body, extras ?? "")); + } + if (models.length === 0) { + throw new Error( + `[schema-gen] Failed to parse any mysqlTable exports from ${SCHEMA_TS}`, + ); + } + return models; +} + +function parseTableBody(name, table, body, extras) { const fields = []; for (const line of body.split("\n")) { const trimmed = line.trim(); - if ( - !trimmed || - trimmed.startsWith("//") || - trimmed.startsWith("@@") || - trimmed.startsWith("///") - ) { - continue; - } - const m = trimmed.match(/^(\w+)\s+(.+)$/); + if (!trimmed || trimmed.startsWith("//")) continue; + const m = trimmed.match(/^(\w+)\s*:\s*(.+?),?\s*$/); if (!m) continue; - const [fieldName, rest] = [m[1], m[2]]; - const typeMatch = rest.match(/^([^\s]+)/); - const prismaType = typeMatch[1]; - const baseType = prismaType.replace(/\?$/, "").replace(/\[\]$/, ""); - const isList = prismaType.endsWith("[]"); - // relation field (points at another model) -> skip - if (modelNames.has(baseType)) continue; + const fieldName = m[1]; + const expr = m[2]; + const colMatch = expr.match(/\(\s*"([^"]+)"/); + if (!colMatch) continue; + const column = colMatch[1]; + + const isBoolean = /\bboolean\s*\(/.test(expr); + const enumMatch = expr.match(/mysqlEnum\s*\(\s*"[^"]+"\s*,\s*(\[[^\]]*\])/); + let enumValues = null; + if (enumMatch) { + try { + enumValues = JSON.parse(enumMatch[1].replace(/'/g, '"')); + } catch { + enumValues = [...enumMatch[1].matchAll(/"([^"]+)"/g)].map((x) => x[1]); + } + } + + let defaultContent = null; + const defIdx = expr.indexOf(".default("); + if (defIdx >= 0) { + const start = defIdx + ".default(".length; + let depth = 0; + for (let i = start; i < expr.length; i++) { + const ch = expr[i]; + if (ch === "(") depth++; + else if (ch === ")") { + if (depth === 0) { + defaultContent = expr.slice(start, i).trim(); + break; + } + depth--; + } + } + } - const attrs = rest; - const optional = prismaType.endsWith("?"); - const map = attrs.match(/@map\(\s*"([^"]+)"\s*\)/)?.[1] ?? fieldName; - const isId = /@id\b/.test(attrs); - const isUnique = /@unique\b/.test(attrs); - const autoIncrement = /@default\(autoincrement\(\)\)/.test(attrs); - const updatedAt = /@updatedAt\b/.test(attrs); - const dbHint = attrs.match(/@db\.(\w+)(?:\((\d+)(?:\s*,\s*(\d+))?\))?/); fields.push({ fieldName, - column: map, - prismaType: baseType, - optional, - isList, - isId, - isUnique, - autoIncrement, - updatedAt, - attrs, - dbHint: dbHint - ? { type: dbHint[1], param1: dbHint[2], param2: dbHint[3] } - : null, + column, + optional: !/\.notNull\s*\(/.test(expr), + isId: /\.primaryKey\s*\(/.test(expr), + isUnique: /\.unique\s*\(/.test(expr), + autoIncrement: /\.autoincrement\s*\(/.test(expr), + isBoolean, + enumValues, + defaultContent, + // legacy shape used by columnExpr / fallback + prismaType: isBoolean + ? "Boolean" + : enumValues + ? fieldName === "gender" + ? "users_gender" + : fieldName === "type" && table === "bans" + ? "bans_type" + : "String" + : "String", + attrs: defaultContent ? `@default(${defaultContent})` : "", + dbHint: null, }); } - // model-level keys - const compIds = body.match(/@@id\(\s*\[([^\]]+)\]\s*\)/)?.[1]; - const ids = compIds - ? compIds.split(",").map((s) => s.trim().replace(/`/g, "")) - : null; - + let ids = null; const uniques = []; - for (const u of body.matchAll(/@@unique\(\s*\[([^\]]+)\]\s*/g)) { - uniques.push(u[1].split(",").map((s) => s.trim().replace(/`/g, ""))); + if (extras) { + const pk = extras.match( + /primaryKey\(\s*\{\s*columns:\s*\[([^\]]+)\]\s*\}\s*\)/, + ); + if (pk) { + ids = [...pk[1].matchAll(/t\.(\w+)/g)].map((m) => m[1]); + } + for (const u of extras.matchAll( + /uniqueIndex\([^)]*\)\.on\(([^)]+)\)/g, + )) { + uniques.push([...u[1].matchAll(/t\.(\w+)/g)].map((m) => m[1])); + } } return { name, table, fields, ids, uniques }; } -const models = modelBlocks.map(parseModel); +const models = parseDrizzleTables(schemaSource); // ---------- Introspect live MySQL ---------- let url; @@ -152,21 +193,20 @@ function quote(v) { /** Map a DB column row to a drizzle column expression string. */ function columnExpr(field, dbCol) { const col = field.column; - let expr = ""; let type = ""; const unsigned = /unsigned/.test(dbCol?.column_type ?? ""); const decimalMatch = dbCol?.column_type?.match(/decimal\((\d+),(\d+)\)/); const enumMatch = dbCol?.column_type?.match(/^enum\((.+)\)$/); - // Prisma enum types -> mysqlEnum if (field.prismaType === "users_gender" || field.prismaType === "bans_type") { use("mysqlEnum"); const values = enumMatch ? [...enumMatch[1].matchAll(/'([^']+)'/g)].map((m) => m[1]) - : field.prismaType === "users_gender" - ? ["M", "F"] - : ["account", "ip", "machine", "super"]; + : (field.enumValues ?? + (field.prismaType === "users_gender" + ? ["M", "F"] + : ["account", "ip", "machine", "super"])); return `mysqlEnum(${quote(col)}, ${JSON.stringify(values)})`; } @@ -179,7 +219,8 @@ function columnExpr(field, dbCol) { break; case "tinyint": { const isBool = - dbCol.column_type === "tinyint(1)" && field.prismaType === "Boolean"; + dbCol.column_type === "tinyint(1)" && + (field.isBoolean || field.prismaType === "Boolean"); if (isBool) { use("boolean"); type = `boolean(${quote(col)})`; @@ -209,8 +250,6 @@ function columnExpr(field, dbCol) { break; case "varchar": case "enum": { - // DB enums become plain varchar in the schema: TS contract is `string` - // (only users_gender / bans_type are typed as mysqlEnum above). use("varchar"); const maxLen = enumMatch ? Math.max( @@ -284,14 +323,13 @@ function columnExpr(field, dbCol) { break; default: console.warn( - `[schema-gen] WARN unhandled DB type "${dbCol?.data_type}" for ${col} (prisma:${field.prismaType})`, + `[schema-gen] WARN unhandled DB type "${dbCol?.data_type}" for ${col}`, ); use("varchar"); type = `varchar(${quote(col)}, { length: 255 })`; } - expr += type; - return expr; + return type; } function modifiers(field, dbCol) { @@ -306,55 +344,35 @@ function modifiers(field, dbCol) { } else if (field.isUnique) { expr += `.unique()`; } - // Mirror the Prisma TS contract: required fields (no `?`) are not-null on - // select, and fields with `@default` are optional on insert. if (!field.optional) { expr += `.notNull()`; } - expr += parseDefault(field, dbCol); + expr += emitDefault(field, dbCol); return expr; } -/** Extract `@default(...)` and emit a drizzle `.default(...)` (or ""). */ -function parseDefault(field, dbCol) { - const m = field.attrs.match(/@default\(/); - if (!m) return ""; - const start = m.index + "@default(".length; - let depth = 0; - let content = ""; - for (let i = start; i < field.attrs.length; i++) { - const ch = field.attrs[i]; - if (ch === "(") { - depth++; - } else if (ch === ")") { - if (depth === 0) { - content = field.attrs.slice(start, i); - break; - } - depth--; - } - } +function emitDefault(field, dbCol) { + const content = field.defaultContent; if (!content) return ""; - if (content === "now()") { + if (content === "sql`CURRENT_TIMESTAMP`" || content.includes("CURRENT_TIMESTAMP")) { markSqlUsed(); return `.default(sql\`CURRENT_TIMESTAMP\`)`; } - if (content === "autoincrement()" || content.startsWith("dbgenerated(")) - return ""; if (content === "true" || content === "false") return `.default(${content})`; + if (/^-?\d+n$/.test(content)) return `.default(${content})`; if (/^-?\d+$/.test(content)) { - // integer literal; bigint64 data type is `bigint`, others are `number` return dbCol?.data_type === "bigint" ? `.default(${content}n)` : `.default(${content})`; } if (/^-?\d+\.\d+$/.test(content)) return `.default(${content})`; - // quoted string or bare enum/string identifier (e.g. @default(M)) - const s = - content.startsWith('"') && content.endsWith('"') - ? content.slice(1, -1) - : content; - return `.default(${JSON.stringify(s)})`; + if ( + (content.startsWith('"') && content.endsWith('"')) || + (content.startsWith("'") && content.endsWith("'")) + ) { + return `.default(${JSON.stringify(content.slice(1, -1))})`; + } + return `.default(${content})`; } function modelTable(model) { @@ -362,8 +380,6 @@ function modelTable(model) { for (const f of model.fields) { const dbCol = colByTable.get(`${model.table}.${f.column}`); if (!dbCol) { - // Column not found in live DB. Prisma schema may be ahead of the DB. - // Fall back to a best-effort type from the Prisma @db hint / base type. const fallback = fallbackColumn(f) + modifiers(f, null); rows.push(`\t${f.fieldName}: ${fallback},`); console.warn( @@ -407,96 +423,23 @@ ${uniqueRows.join("\n")} ], );`; } - return `export const ${model.name} = mysqlTable( - "${model.table}", - { + return `export const ${model.name} = mysqlTable("${model.table}", { ${rows.join("\n")} - }, -);`; +});`; } function fallbackColumn(field) { - const hint = field.dbHint; const name = field.column; - switch (hint?.type) { - case "VarChar": - use("varchar"); - return `varchar(${quote(name)}, { length: ${Number(hint.param1 ?? 191)} })`; - case "Char": - use("char"); - return `char(${quote(name)}, { length: ${Number(hint.param1 ?? 8)} })`; - case "Text": - use("text"); - return `text(${quote(name)})`; - case "MediumText": - use("mediumtext"); - return `mediumtext(${quote(name)})`; - case "LongText": - use("longtext"); - return `longtext(${quote(name)})`; - case "Decimal": - use("decimal"); - return `decimal(${quote(name)}, { precision: ${Number(hint.param1 ?? 10)}, scale: ${Number(hint.param2 ?? 0)}, mode: "number" })`; - case "UnsignedBigInt": - use("bigint"); - return `bigint(${quote(name)}, { mode: "bigint", unsigned: true })`; - case "UnsignedInt": - use("int"); - return `int(${quote(name)}, { unsigned: true })`; - case "Double": - use("double"); - return `double(${quote(name)})`; - case "Float": - use("float"); - return `float(${quote(name)})`; - case "Json": - use("json"); - return `json(${quote(name)})`; - case "Timestamp": - use("timestamp"); - return `timestamp(${quote(name)})`; - case "Time": - return `timeAsDate(${quote(name)})`; - case "Date": - return `dateAsDate(${quote(name)})`; - case "TinyInt": - use("tinyint"); - return `tinyint(${quote(name)})`; - default: - break; + if (field.enumValues) { + use("mysqlEnum"); + return `mysqlEnum(${quote(name)}, ${JSON.stringify(field.enumValues)})`; } - switch (field.prismaType) { - case "Int": - use("int"); - return `int(${quote(name)})`; - case "BigInt": - use("bigint"); - return `bigint(${quote(name)}, { mode: "bigint" })`; - case "Boolean": - use("boolean"); - return `boolean(${quote(name)})`; - case "DateTime": - use("datetime"); - return `datetime(${quote(name)})`; - case "String": - use("varchar"); - return `varchar(${quote(name)}, { length: 255 })`; - case "Float": - use("double"); - return `double(${quote(name)})`; - case "Decimal": - use("decimal"); - return `decimal(${quote(name)}, { precision: 10, scale: 2, mode: "number" })`; - case "Json": - use("json"); - return `json(${quote(name)})`; - case "Bytes": - use("binary"); - return `binary(${quote(name)})`; - default: - use("varchar"); - return `varchar(${quote(name)}, { length: 255 })`; + if (field.isBoolean || field.prismaType === "Boolean") { + use("boolean"); + return `boolean(${quote(name)})`; } + use("varchar"); + return `varchar(${quote(name)}, { length: 255 })`; } // ---------- Generate file ---------- @@ -532,8 +475,8 @@ const IMPORTABLE = [ const importList = IMPORTABLE.filter((b) => used.has(b)); const helpers = [ - "// MySQL TIME / DATE columns are hydrated by Prisma as JS Date (epoch 1970-01-01", - "// for TIME). Mirror that so existing call sites keep working unchanged.", + "// MySQL TIME / DATE columns are hydrated as JS Date (epoch 1970-01-01", + "// for TIME). Keep this so existing call sites stay unchanged.", "const timeAsDate = customType<{ data: Date; driverData: string }>({", " dataType() {", ' return "time";', @@ -563,13 +506,15 @@ const helpers = [ "", ].join("\n"); +const sqlImport = usedSql ? 'import { sql } from "drizzle-orm";\n' : ""; + const out = `// AUTO-GENERATED by scripts/generate-drizzle-schema.mjs — DO NOT EDIT. -// TS field names mirror prisma/schema.prisma (camelCase); column names are the -// real MySQL columns. Run \`pnpm db:schema:generate\` after schema changes. -import { +// TS field names come from the previous src/db/schema.ts; column types from the +// live MySQL DB. Run \`pnpm db:schema:generate\` after schema/map changes. +${sqlImport}import { ${importList.map((b) => `\t${b},`).join("\n")} } from "drizzle-orm/mysql-core"; -${usedSql ? `import { sql } from "drizzle-orm";\n` : ""} + ${helpers}${body} `; diff --git a/scripts/migration-idempotency.test.ts b/scripts/migration-idempotency.test.ts index 5102f2a0..1fcf2bb1 100644 --- a/scripts/migration-idempotency.test.ts +++ b/scripts/migration-idempotency.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; describe("radio columns migration", () => { it("adds every column idempotently for partially migrated databases", () => { const sql = readFileSync( - resolve("prisma/migrations/0009_radio_contests_giveaways_columns.sql"), + resolve("drizzle/migrations/0009_radio_contests_giveaways_columns.sql"), "utf8", ); const additions = sql.match(/ADD COLUMN(?! IF NOT EXISTS)/gi) ?? []; diff --git a/src/app/(site)/leaderboard/page.tsx b/src/app/(site)/leaderboard/page.tsx index 4ddaec82..22b6297f 100644 --- a/src/app/(site)/leaderboard/page.tsx +++ b/src/app/(site)/leaderboard/page.tsx @@ -10,7 +10,7 @@ export const revalidate = 300; export const metadata = { title: "Leaderboard" }; -// Currency type ids (see prisma/schema.prisma UsersCurrency): +// Currency type ids (see UsersCurrency in src/db/schema.ts): // Credits = -1 (lives on users.credits), Duckets = 0, Diamonds = 5. const TABS = [ { key: "credits", label: "Credits", coin: "credits" }, diff --git a/src/app/(site)/photos/page.tsx b/src/app/(site)/photos/page.tsx index 54d825e0..8b382a03 100644 --- a/src/app/(site)/photos/page.tsx +++ b/src/app/(site)/photos/page.tsx @@ -35,7 +35,7 @@ export default async function PhotosPage() { } // Pre-shape for the client lightbox: translate captions server-side so the - // client component stays free of i18n/prisma dependencies. + // client component stays free of i18n/db dependencies. const items: LightboxPhoto[] = photos.map((p) => ({ id: String(p.id), url: p.url, diff --git a/src/components/public/photo-lightbox.tsx b/src/components/public/photo-lightbox.tsx index 333f6e92..c5be7891 100644 --- a/src/components/public/photo-lightbox.tsx +++ b/src/components/public/photo-lightbox.tsx @@ -10,7 +10,7 @@ import { useCallback, useEffect, useState } from "react"; * toggled by state — closes on backdrop click, on the ✕ button, or with Esc. * * Photos are pre-shaped on the server (caption strings already translated) so - * this component carries no prisma/i18n dependency. + * this component carries no db/i18n dependency. */ export type LightboxPhoto = { id: string; diff --git a/src/components/public/radio-player.tsx b/src/components/public/radio-player.tsx index 35d317bc..4841404c 100644 --- a/src/components/public/radio-player.tsx +++ b/src/components/public/radio-player.tsx @@ -10,7 +10,7 @@ import { useEventSource } from "@/lib/use-event-source"; * radio via a hidden