Files
EpicNext-Cms/README.md
T
openhands 9a8905c726
CI / check (push) Successful in 28s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m35s
docs: update README for Drizzle ORM migration
- Document Drizzle ORM as primary data layer with CLI usage examples
- Add Prisma compatibility facade section (backwards compatibility)
- Document legacy Prisma CLI removal (migrate dev, studio, db push no longer used)
- Update architecture tree with src/db/ and scripts/ directories
- Update migration count (19 SQL files)
- Add contributing guidelines for Drizzle-based code
2026-07-31 14:26:09 +02:00

15 KiB

EpicNext-CMS v1.0.1

A modern, high-performance content management system for Habbo hotel emulators, built on Next.js 16 (App Router) with Drizzle ORM and React 19. Designed to integrate seamlessly with Polaris / Arcturus Morningstar MySQL/MariaDB databases.

Features a premium animated homepage (typewriter hero, floating orbs, scroll counters), a full admin panel, NextAuth authentication (bcrypt with MD5-to-bcrypt upgrade), real-time RCON communication, Server-Sent Events for live radio data, smooth page transitions, and PM2 production deployment.


System Requirements

Component Version Notes
Node.js >= 22 Required by Next.js 16
pnpm >= 10.33.4 Package manager (npm/yarn not supported)
MySQL / MariaDB 8.0+ / 10.6+ Shared with the emulator
Redis 7.x+ Optional — caching, rate limiting, SSE
Java 17+ Required only if building the emulator
Maven 3.9+ Required only if building the emulator

Quick Start

1. Clone & Install

git clone https://gitlab.epicnabbo.nl/remco/EpicNext-Cms.git
cd EpicNext-Cms
pnpm install

2. Database Setup

The CMS shares a database with the Polaris/Arcturus emulator. Use an existing database or create a new one:

CREATE DATABASE IF NOT EXISTS epicnext_cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

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/.

3. Configure Environment

cp .env.example .env

Edit .env with at minimum:

DATABASE_URL=mysql://user:[email protected]:3306/epicnext_cms
AUTH_SECRET=<random 32+ character string>
HOTEL_NAME=YourHotel
APP_URL=http://localhost:3000

See .env.example for all optional variables (RCON, email, Redis, OAuth, PayPal, etc.).

4. ORM Setup & Type Generation

Drizzle ORM (Primary Data Layer)

Drizzle ORM is the runtime data layer. The connection is a singleton in src/lib/db.ts:

import { db } from "@/lib/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm/expressions";

const found = await db.select()
  .from(users)
  .where(eq(users.username, "hello"));

Drizzle CLI (drizzle-kit) is used for local development tasks — it is a devDependency and is never bundled in production.

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

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).

Prisma Compatibility Facade (Backwards Compatibility)

A Prisma-compatible facade at @/lib/prisma allows existing code to keep calling prisma.users.findMany() without refactoring. At runtime, the facade routes every query through Drizzle. There is zero Prisma client or query-engine overhead in production.

Generate the Prisma type stubs used for type-checking the facade:

pnpm prisma:generate

This creates src/generated/prisma/ (a local, gitignored build artifact) with TypeScript types only. It is never shipped in the production bundle.

Legacy CLI command removed: The prisma CLI is now a devDependency used only for type generation. Old commands such as prisma migrate dev, prisma studio, or prisma db push are no longer applicable — use the SQL migration runner (pnpm db:migrate) or Drizzle CLI instead.

5. Run CMS Migrations

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.

Check migration status:

pnpm db:migrate:status

6. Build & Start

# Development (hot reload)
pnpm dev

# Production
pnpm build && pnpm start

Open http://localhost:3000 in your browser.

7. First Login

  1. Register an account at /register, or log in with an existing emulator account.
  2. Grant yourself admin access: UPDATE users SET rank = 7 WHERE username = 'yourname';
  3. Visit /admin and configure your hotel via Admin → CMS Settings.

Production Deployment (PM2)

pnpm build
pm2 start pnpm --name "next" -- start
pm2 save

Restart after updates:

git pull
pnpm install
pnpm build
pm2 restart next

The CMS runs behind an nginx reverse proxy on the default port 3000. Static assets (media uploads) are persisted via /api/media/* and survive rebuilds.


Scripts

Command Description
pnpm dev Start development server (hot reload)
pnpm build Production build
pnpm start Start production server
pnpm typecheck Run TypeScript type checking
pnpm test Run all tests (Vitest)
pnpm db:migrate Apply pending SQL migrations
pnpm db:migrate:status Show migration status
pnpm prisma:generate Regenerate Prisma type stubs (dev only, not prod)
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

Performance Features

Feature Description
React Compiler Automatic memoization — reduces unnecessary re-renders
View Transitions API Native browser transitions between page navigations
Lenis Smooth Scroll Fluid, customizable scrolling (respects prefers-reduced-motion)
Server-Sent Events Real-time radio now-playing & listeners via SSE (no polling)
Redis Caching Caches API responses (home, radio config) up to 30s in Redis
Bundle Analyzer Run pnpm analyze to visualize and optimize bundle sizes
RCON (TCP Socket) Live commands to the emulator (credits, badges, kick, ban)
Streaming & Suspense Next.js App Router streaming for fast page loads
Automatic Image Optimization next/image with Sharp for resizing and WebP/AVIF
CSS-based Animations input-glow, btn-shine, Reveal scroll animations, floating orbs
Compression Gzip compression enabled on all responses
Stale Times Optimized router cache (30s dynamic, 180s static)
PWA Service worker with skip-waiting, stale CSS chunk recovery
Biome Fast linting and formatting (replaces ESLint + Prettier)

Architecture

├── prisma/
│   ├── schema.prisma          # ~190 models (emulator + CMS) — used for type generation only (legacy)
│   └── migrations/            # 19 SQL migrations for CMS tables (idempotent, never re-run)
├── 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 # One-off: generate src/db/schema.ts from schema.prisma
├── src/
│   ├── db/
│   │   ├── schema.ts          # Drizzle ORM schema (176 tables — runtime data layer)
│   │   └── migrations/        # Drizzle migration files (local dev only)
│   ├── app/                   # Next.js App Router (pages & API routes)
│   ├── actions/               # Server Actions
│   ├── components/            # UI components
│   ├── lib/
│   │   ├── auth/              # NextAuth, password hashing, 2FA, SSO tickets
│   │   ├── services/          # RCON, email, currency, PayPal, alerts
│   │   ├── prisma.ts          # Prisma-compatible facade (routes to Drizzle at runtime)
│   │   ├── prisma-facade.ts   # Drizzle-backed implementation of Prisma API surface
│   │   ├── db.ts              # Drizzle connection singleton (runtime)
│   │   ├── redis.ts           # Redis client (ioredis)
│   │   ├── redis-cache.ts     # Redis caching utility for API routes
│   │   ├── cache.ts           # In-memory cache fallback
│   │   ├── motion.ts          # Framer Motion animation variants
│   │   └── use-event-source.ts # React hook for SSE subscriptions
│   ├── messages/              # i18n translations (en, nl, de, fr, es, it, pt, da, no, sv)
│   └── env.ts                 # Zod-validated environment schema
├── public/
│   ├── assets/                # Images, icons, fonts
│   └── scripts/               # Client-side scripts (theme-init.js)
├── update-Nitrov3.sh          # Emulator & Nitro updater utility
├── next.config.ts             # Next.js configuration
└── .env.example               # Environment template with all options

Database Ownership

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)
Migration tracking cms_migrations table Auto-created by migration runner

ORM Architecture

The CMS uses a dual-layer approach:

  • Runtime (Drizzle ORM): @/lib/db exposes a Drizzle singleton connected to the MySQL/MariaDB database. All new code should use this directly. The schema is defined in src/db/schema.ts with 176 tables typed against the existing database columns.
  • Legacy Compatibility (Prisma Facade): @/lib/prisma provides a Prisma-compatible API surface backed by Drizzle. This allows existing code to continue working without refactoring. The facade (@/lib/prisma-facade.ts) implements the Prisma client API (findMany, findUnique, create, $transaction, $queryRaw, etc.) but routes all queries through Drizzle at runtime — no Prisma client engine or query engine is loaded in production.
  • Type Generation: src/generated/prisma/ (regenerated via pnpm prisma:generate) exists solely for TypeScript type-checking. It is gitignored and is never bundled in the production build.

Migration path: new database access should use import { db } from "@/lib/db" with queries built via src/db/schema.ts. The facade is maintained for backwards compatibility but is not recommended for new code.


Optional Integrations

Emulator — RCON

For live actions (credits, badges, rank changes, kick, ban, hotel alerts). The emulator must be running with RCON enabled at RCON_HOST:RCON_PORT.

Client — Nitro

The /client page loads the Nitro client. Configure the client URL via Admin → CMS Settings (nitro_client_url). See update-Nitrov3.sh for deployment.

Radio

Supports any streaming radio (e.g., Azuracast). Configure endpoints via CMS Settings:

  • radio_now_playing_api_url
  • radio_listeners_api_url

The radio player uses SSE for real-time updates (10s interval, no polling).

Background Jobs

Run as a persistent process:

pnpm jobs:worker

Scheduled tasks:

  • Daily 03:00 — Emulator JAR backup (requires EMULATOR_JAR_PATH + EMULATOR_BACKUP_DIR)
  • Daily 04:00 — Cleanup login logs (>30 days) and expired password reset tokens (>7 days)

CAPTCHA

Supports Cloudflare Turnstile and Google reCAPTCHA. Configure via CMS Settings.

Theming

12 preset themes with fully customizable colors via Admin → Theme (/admin/theme).

AI Content Moderation

Optional OpenAI-powered moderation for comments and guestbook posts. Set OPENAI_API_KEY.


Development

pnpm dev          # Start with hot reload
pnpm typecheck    # Type check all files
pnpm test         # Run test suite
pnpm analyze      # Build and analyze bundle sizes
pnpm biome:check  # Lint and format

Contributing

  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
  5. For new database code, use the Drizzle runtime directly (import { db } from "@/lib/db") — see ORM Setup
  6. Avoid any — use eslint-disable or biome-ignore comments only when unavoidable (e.g., Prisma facade compatibility)

License

CC BY-NC-SA 4.0. See LICENSE for details.