573 lines
22 KiB
Markdown
573 lines
22 KiB
Markdown
# 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 (argon2id hashing with legacy md5/bcrypt auto-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 | 26.8.1 | Current release pinned in `.nvmrc` |
|
|
| pnpm | >= 11.25.0 | Recommended package manager |
|
|
| npm | >= 11.x | Supported alternative |
|
|
| yarn | >= 4.x | Supported alternative |
|
|
| MySQL / MariaDB | 8.0+ / 10.6+ | Shared with the emulator |
|
|
| Docker | 24+ | Optional — for containerized deployment |
|
|
| DragonflyDB | 1.x+ | Optional — caching, rate limiting, SSE |
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### 1. Clone & Install
|
|
|
|
Choose your preferred package manager:
|
|
|
|
```bash
|
|
git clone https://gitlab.epicnabbo.nl/remco/EpicNext-Cms.git
|
|
cd EpicNext-Cms
|
|
```
|
|
|
|
**pnpm (recommended):**
|
|
```bash
|
|
pnpm install
|
|
```
|
|
|
|
**npm:**
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
**yarn:**
|
|
```bash
|
|
yarn install
|
|
```
|
|
|
|
### 2. Database Setup
|
|
|
|
The CMS shares a database with the Polaris/Arcturus emulator. Use an existing database or create a new one:
|
|
|
|
```sql
|
|
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 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/`.
|
|
|
|
### 3. Configure Environment
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Edit `.env` with at minimum:
|
|
|
|
```dotenv
|
|
DATABASE_URL=mysql://user:[email protected]:3306/epicnext_cms
|
|
AUTH_SECRET=<random 32+ character string>
|
|
HOTEL_NAME=YourHotel
|
|
APP_URL=http://localhost:3002
|
|
```
|
|
|
|
See `.env.example` for all optional variables (RCON, email, DragonflyDB, OAuth, PayPal, etc.).
|
|
|
|
### 4. Run CMS Migrations
|
|
|
|
```bash
|
|
# pnpm
|
|
pnpm db:migrate
|
|
|
|
# npm
|
|
npm run db:migrate
|
|
|
|
# yarn
|
|
yarn db:migrate
|
|
```
|
|
|
|
Creates all CMS-owned tables (`website_*`, `radio_*`, `acl_*`, `admin_audit_log`, etc.) via idempotent SQL files in `drizzle/migrations/`. Emulator tables are never touched.
|
|
|
|
### 5. Build & Start
|
|
|
|
```bash
|
|
# Development (hot reload)
|
|
pnpm dev # or: npm run dev / yarn dev
|
|
|
|
# Production
|
|
pnpm build && pnpm start # or: npm run build && npm start
|
|
```
|
|
|
|
Open `http://localhost:3002` in your browser.
|
|
|
|
### 6. 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**.
|
|
|
|
---
|
|
|
|
## Docker Deployment
|
|
|
|
The CMS ships with a multi-stage Dockerfile that automatically detects your package manager (pnpm, npm, or yarn) based on which lockfile is present. Only the **CMS** runs in Docker; the Nitro/Octane client and its renderer stay outside and are served by nginx on the host.
|
|
|
|
### Prerequisites
|
|
|
|
- Docker 24+ and Docker Compose v2
|
|
- `.env` file configured (see step 3 above)
|
|
- A MySQL/MariaDB database reachable from the container (`DATABASE_URL` host should point to the DB server, not `127.0.0.1` unless it's reachable from inside the container)
|
|
- (Optional) A shared `Gamedata` directory at `/var/www/Gamedata` with write access (see [Volumes](#volumes) below)
|
|
|
|
### Quick Start with Docker
|
|
|
|
```bash
|
|
# 1. Clone and configure
|
|
git clone https://gitlab.epicnabbo.nl/remco/EpicNext-Cms.git
|
|
cd EpicNext-Cms
|
|
cp .env.example .env
|
|
# Edit .env with your database credentials and settings.
|
|
# The container runs on the host network, so all 127.0.0.1 references
|
|
# (DATABASE_URL, REDIS_URL, RCON, imaging) keep pointing at the host as-is.
|
|
|
|
# 2. Stop any existing host-side CMS that occupies port 3002 (if present).
|
|
pm2 stop next 2>/dev/null || true
|
|
|
|
# 3. Ensure the write directories are owned by www-data (UID/GID 33) so the
|
|
# container user can write imports/uploads to them.
|
|
sudo chown -R 33:33 ./public/nitro-assets ./public/swf ./storage
|
|
|
|
# 4. Build and start
|
|
docker compose up -d --build
|
|
|
|
# 5. Run migrations. The slim runtime image has no source/tsx, so run migrations
|
|
# on the HOST (against the same database) before/after starting the container.
|
|
pnpm db:migrate # or: npm run db:migrate / yarn db:migrate
|
|
|
|
# 6. Check logs
|
|
docker compose logs -f cms
|
|
```
|
|
|
|
The CMS will be available at `http://localhost:3002`.
|
|
|
|
> **Reverse proxy:** This setup is designed to run behind the existing nginx on the host. nginx serves the Nitro/Octane client (`/client/`, `/nitro-client/`) and `/gamedata/` directly from `/var/www/Octane/dist` and `/var/www/Gamedata`, and proxies `/` to the CMS on `127.0.0.1:3002`. Point `APP_URL`/`NEXT_PUBLIC_APP_URL` at the public site URL.
|
|
|
|
### Automatic Updates
|
|
|
|
A cron-safe update script is included: `scripts/docker-update.sh`. It pulls the
|
|
latest `main`, runs CMS migrations on the host, rebuilds the image, recreates the
|
|
container, waits for a healthy status, and makes sure the stale host-side PM2 CMS
|
|
(`pm2 stop next`) stays stopped so it can't clash on port 3002.
|
|
|
|
```bash
|
|
./scripts/docker-update.sh # run once manually
|
|
# log: logs/docker-update.log
|
|
```
|
|
|
|
Set it up as a nightly cron job (03:30 example):
|
|
|
|
```bash
|
|
crontab -e
|
|
# 30 3 * * * /var/www/atom-nexst/scripts/docker-update.sh >> /var/www/atom-nexst/logs/docker-update.cron.log 2>&1
|
|
```
|
|
|
|
The script aborts safely (exit 1) if the working tree has uncommitted changes so
|
|
a `git pull` can never clobber local edits, and leaves the container running if
|
|
health fails so you can debug it (exit 3).
|
|
|
|
### Docker Commands
|
|
|
|
| Command | Description |
|
|
| ------------------------------------------ | ------------------------------------ |
|
|
| `docker compose up -d --build` | Build and start in background |
|
|
| `docker compose down` | Stop and remove containers |
|
|
| `docker compose logs -f cms` | Follow CMS logs |
|
|
| `docker compose exec cms sh` | Open a shell in the CMS container |
|
|
| `docker compose restart cms` | Restart the CMS container |
|
|
| `docker compose pull && docker compose up -d --build` | Update and redeploy |
|
|
| `pnpm db:migrate` (on the **host**) | Run database migrations (slim image has no source) |
|
|
| `./scripts/docker-update.sh` | Full automated update (manual or cron) |
|
|
|
|
### How Package Manager Detection Works
|
|
|
|
The Dockerfile checks for lockfiles in this order:
|
|
|
|
1. **`pnpm-lock.yaml`** → uses pnpm (fastest, recommended)
|
|
2. **`yarn.lock`** → uses yarn
|
|
3. **`package-lock.json`** → uses npm
|
|
4. **No lockfile** → falls back to `npm install`
|
|
|
|
This means you can use any package manager on your host machine — the Docker build will automatically match.
|
|
|
|
> Override the detection explicitly with `docker compose build --build-arg PACKAGE_MANAGER=pnpm` (or `npm` / `yarn`).
|
|
|
|
### Volumes
|
|
|
|
Only the CMS runs in Docker. The heavy client assets and gamedata stay on the host and are shared into the container so imports and uploads persist:
|
|
|
|
| Container Path | Host Path | Mode | Purpose |
|
|
| -------------------------- | -------------------------- | ---- | ------------------------------------ |
|
|
| `/app/public/nitro-assets` | `./public/nitro-assets` | rw | Imported furni/pet/effect assets |
|
|
| `/app/public/swf` | `./public/swf` | rw | SWF costumes / icons (imports) |
|
|
| `/app/storage` | `./storage` | rw | Uploaded media (persistent) |
|
|
| `/var/www/Gamedata` | `/var/www/Gamedata` | rw | Shared gamedata root (hardcoded path)|
|
|
|
|
**About the hardcoded `/var/www/Gamedata` path:** `src/lib/services/furni-asset-dirs.ts` defines `DEFAULT_GAMEDATA_ROOT = /var/www/Gamedata` as an absolute on-disk path the CMS reads and mirrors imported assets into. The compose file mounts the host `/var/www/Gamedata` at the identical path inside the container so `existsSync('/var/www/Gamedata')` succeeds and nginx keeps serving `/gamedata/` from the same directory.
|
|
|
|
**The Nitro/Octane client and renderer are NOT mounted** — nginx on the host serves them directly:
|
|
|
|
| Component | Host path | How it's served |
|
|
| -------------------- | ----------------------- | ---------------------------------------------------------------------- |
|
|
| Nitro/Octane client | `/var/www/Octane/dist` | nginx `location ^~ /client/` and `/nitro-client/` alias |
|
|
| Octane-Renderer | `/var/www/Octane-Renderer` | Optional. Run separately (or as the commented-out `imager` service) proxied by nginx `/imaging` |
|
|
| Camera uploads | `/var/www/Camera` | nginx `/camera/` alias |
|
|
|
|
**Container user & write permissions:** the CMS container runs as `www-data` (UID/GID 33) by default to match the host owner of `/var/www/Gamedata`. Ensure the other write volumes (`./public/nitro-assets`, `./public/swf`, `./storage`) are also owned by `www-data` on the host:
|
|
|
|
```bash
|
|
sudo chown -R 33:33 ./public/nitro-assets ./public/swf ./storage
|
|
sudo chmod -R o+rX ./public/nitro-assets ./public/swf ./storage
|
|
```
|
|
|
|
If your host user UID differs, override the run user at build time (defaults to `www-data`, which already exists in the base image with UID/GID 33):
|
|
|
|
```bash
|
|
docker compose build --build-arg RUN_USER=1000
|
|
```
|
|
|
|
### Networking
|
|
|
|
The CMS container runs with `network_mode: host`. This lets the existing `127.0.0.1` references in `.env` keep working against host services — MariaDB (`3306`), DragonflyDB/Redis (`6379`), the emulator RCON/API (`3003`/`3001`) and the avatar imager — without re-writing any environment variables. The container consequently listens **directly on the host's port 3002**, so stop any previous host-side CMS (e.g. `pm2 stop next`) that occupies that port before starting it.
|
|
|
|
The **build** also runs on the host network (`build.network: host`) because this host disables Docker iptables (`/etc/docker/daemon.json`: `"iptables": false`), which would otherwise leave build containers without outbound NAT/DNS when fetching packages.
|
|
|
|
### Health Check
|
|
|
|
The container includes a health check that hits `/api/health` on port 3002 every 30 seconds (and reports DB/Redis/emulator status). Check status with:
|
|
|
|
```bash
|
|
docker inspect --format='{{.State.Health.Status}}' epicnext-cms
|
|
```
|
|
|
|
|
|
---
|
|
|
|
## 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`:
|
|
|
|
```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 |
|
|
| ---------------------- | ---------------------------------------------------------------------- |
|
|
| `pnpm db:generate` | Draft SQL from Drizzle schema into `drizzle/drafts/` (review + copy) |
|
|
| `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 + live DB |
|
|
|
|
> 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`.
|
|
|
|
---
|
|
|
|
## DragonflyDB (Caching, Rate Limiting, SSE)
|
|
|
|
DragonflyDB is a drop-in, Redis-compatible in-memory datastore. The CMS connects to it via `REDIS_URL` using the `ioredis` client. It is optional: without it the CMS falls back to in-process memory.
|
|
|
|
### Install (Ubuntu/Debian)
|
|
|
|
```bash
|
|
curl -fsSL -o /tmp/dragonfly_amd64.deb \
|
|
https://github.com/dragonflydb/dragonfly/releases/download/v1.40.1/dragonfly_amd64.deb
|
|
apt-get install -y /tmp/dragonfly_amd64.deb
|
|
systemctl enable --now dragonfly
|
|
```
|
|
|
|
### Configure
|
|
|
|
Edit `/etc/dragonfly/dragonfly.conf`:
|
|
|
|
```ini
|
|
--bind=127.0.0.1
|
|
--port=6379
|
|
--maxmemory=2gb
|
|
--version_check=false
|
|
```
|
|
|
|
### Point the CMS at it
|
|
|
|
```dotenv
|
|
REDIS_URL=redis://127.0.0.1:6379?connect_timeout=2
|
|
```
|
|
|
|
Verify via `/api/health` — it should report `"redis": true`.
|
|
|
|
---
|
|
|
|
## Nginx Configuration
|
|
|
|
The CMS is designed to run behind an nginx reverse proxy. Below is a reference configuration.
|
|
|
|
### Prerequisites
|
|
|
|
- SSL certificates in `/etc/ssl/cert.pem` and `/etc/ssl/key.pem` (or use Let's Encrypt)
|
|
- CMS running on `127.0.0.1:3002`
|
|
|
|
### Reference Configuration
|
|
|
|
```nginx
|
|
proxy_cache_path /var/cache/nginx/html_cache levels=1:2 keys_zone=html_cache:50m max_size=500m inactive=10m use_temp_path=off;
|
|
|
|
server {
|
|
listen 80;
|
|
server_name yourdomain.com;
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl;
|
|
http2 on;
|
|
server_name yourdomain.com;
|
|
|
|
ssl_certificate /etc/ssl/cert.pem;
|
|
ssl_certificate_key /etc/ssl/key.pem;
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
client_max_body_size 20m;
|
|
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3002;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_cache html_cache;
|
|
proxy_cache_valid 200 60s;
|
|
proxy_ignore_headers Vary;
|
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
|
add_header X-Cache-Status $upstream_cache_status always;
|
|
}
|
|
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:3002;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
}
|
|
|
|
location /_next/static/ {
|
|
proxy_pass http://127.0.0.1:3002;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
|
|
location /imaging {
|
|
proxy_pass http://127.0.0.1:3030;
|
|
add_header Cache-Control "public, max-age=3600, s-maxage=86400";
|
|
}
|
|
|
|
location /health {
|
|
access_log off;
|
|
return 200 "OK";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Production Deployment (PM2)
|
|
|
|
```bash
|
|
pnpm build
|
|
pm2 start pnpm --name "next" -- start
|
|
pm2 save
|
|
```
|
|
|
|
Restart after updates:
|
|
|
|
```bash
|
|
git pull
|
|
pnpm install
|
|
pnpm build
|
|
pm2 restart next
|
|
```
|
|
|
|
---
|
|
|
|
## 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 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 |
|
|
| `pnpm biome:check` | Lint and format code |
|
|
|
|
> Replace `pnpm` with `npm run` or `yarn` for other package managers.
|
|
|
|
---
|
|
|
|
## 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 |
|
|
| **Server-Sent Events** | Real-time radio now-playing & listeners via SSE |
|
|
| **DragonflyDB Caching** | Caches API responses up to 30s in DragonflyDB |
|
|
| **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 |
|
|
| **Compression** | Gzip compression enabled on all responses |
|
|
| **PWA** | Service worker with skip-waiting, stale CSS chunk recovery |
|
|
| **Biome** | Fast linting and formatting (replaces ESLint + Prettier) |
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
├── 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
|
|
│ └── generate-drizzle-schema.mjs # Regen src/db/schema.ts from schema + live DB
|
|
├── src/
|
|
│ ├── db/
|
|
│ │ ├── 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
|
|
│ ├── lib/
|
|
│ │ ├── auth/ # NextAuth, password hashing, 2FA, SSO tickets
|
|
│ │ ├── services/ # RCON, email, currency, PayPal, alerts
|
|
│ │ ├── db.ts # Drizzle connection singleton (runtime)
|
|
│ │ ├── redis.ts # Cache client (ioredis → DragonflyDB)
|
|
│ │ └── motion.ts # Framer Motion animation variants
|
|
│ ├── messages/ # i18n translations (en, nl, de, fr, es, it, pt, da, no, sv)
|
|
│ └── env.ts # Zod-validated environment schema
|
|
├── public/
|
|
│ ├── assets/ # Images, icons, fonts
|
|
│ ├── nitro-assets/ # Nitro client assets (~3GB, volume-mounted in Docker)
|
|
│ └── swf/ # SWF client files (volume-mounted in Docker)
|
|
├── docker-compose.yml # Docker Compose configuration
|
|
├── Dockerfile # Multi-stage, multi-package-manager Docker build
|
|
├── next.config.ts # Next.js configuration
|
|
└── .env.example # Environment template with all options
|
|
```
|
|
|
|
---
|
|
|
|
## 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:
|
|
|
|
```bash
|
|
pnpm jobs:worker # or: npm run jobs:worker / yarn jobs:worker
|
|
```
|
|
|
|
### AI Content Moderation
|
|
|
|
Optional OpenAI-powered moderation for comments and guestbook posts. Set `OPENAI_API_KEY`.
|
|
|
|
### FlareSolverr (Cloudflare Bypass)
|
|
|
|
Some clone sources are protected by Cloudflare. FlareSolverr solves these challenges automatically.
|
|
|
|
```bash
|
|
docker compose up -d flaresolverr
|
|
```
|
|
|
|
```dotenv
|
|
FLARESOLVERR_URL=http://localhost:8191
|
|
```
|
|
|
|
### Furniture Import with Auto-Translation
|
|
|
|
The CMS automatically translates furniture names and descriptions to **13 languages** on every import:
|
|
|
|
- **Native languages** (via official Habbo gamedata): Dutch, English, German, French, Spanish, Turkish, Italian
|
|
- **Additional languages** (via LibreTranslate Docker at `127.0.0.1:5000`): Portuguese, Finnish, Polish, Russian, Arabic, Japanese
|
|
|
|
### Theming
|
|
|
|
12 preset themes with fully customizable colors via **Admin → Theme** (`/admin/theme`).
|
|
|
|
---
|
|
|
|
## Development
|
|
|
|
```bash
|
|
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)
|
|
3. SQL migrations in `drizzle/migrations/` must be idempotent
|
|
4. For new database code, use the Drizzle runtime directly (`import { db } from "@/lib/db"`)
|
|
5. Avoid `any` — use `biome-ignore` comments only when unavoidable
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
CC BY-NC-SA 4.0. See `LICENSE` for details.
|