# OptRL Site

This is the OptRL official site + RL playgrounds (Next.js 14).

## Project setup

### Prerequisites

- Node.js 20+ (this repo is developed on Node `v20.x`) — [download](https://nodejs.org/en/download)
- npm 10+

### Install

```bash
npm ci
```

### Environment variables

This project uses `env-cmd`, which loads `.env` / `.env.local` when running scripts.

- Copy `.env.example` → `.env.local` and fill values:

```bash
cp .env.example .env.local
```

Minimum recommended:

- `ADMIN_SESSION_SECRET` (recommended for stable admin sessions)
- `SMTP_*` + `CONTACT_TO` (required for the contact form `/api/contact`)
- `CMS_DB_PATH` (optional; defaults to `content/cms.sqlite`)
- Bare metal recommendation: use an absolute path (for example `/var/lib/optrl/cms.sqlite`)
- `CMS_WRITE_POSTS_TO_DISK` (optional; defaults to `true`)
- `CMS_WRITE_IMAGES_TO_DISK` (optional; defaults to `false`)
- `CMS_MAX_IMAGE_BYTES` (optional; defaults to `10485760` / 10MB)

### Run locally

```bash
npm run dev
```

Open:

- Home: `http://localhost:3000`
- Blog: `http://localhost:3000/blog`
- Admin: `http://localhost:3000/admin`

### Useful commands

```bash
# lint
npm run lint

# production build
npm run build

# serve the static "out/" build (useful for quick previews)
npm run start

# standalone build + run (Node server output)
npm run build:standalone
npm run start:standalone
```

### Bare-metal deploy (Node + systemd)

For the SQLite-backed admin/blog CMS on a bare-metal server:

- Use the standalone Node server (`npm run build:standalone`)
- Install `sqlite3` on the server (the app invokes the `sqlite3` CLI)
- Prefer an absolute `CMS_DB_PATH` (for example `/var/lib/optrl/cms.sqlite`)
- If `CMS_DB_PATH` is relative, it resolves from `process.cwd()` (set `systemd` `WorkingDirectory` to the project root)

Example unit template:

- `deploy/systemd/optrl-site.service.example`

### Deploy scripts

- GitHub Pages-style static deploy output in `docs/`:
  - `npm run deploy:gh` (sets `NEXT_PUBLIC_BASE_PATH=/reinforcelab`)
  - `npm run deploy:optrl` (no base path)

## Local dev

```bash
npm run dev
```

## Blog admin (/admin)

The site includes a simple local admin UI to create/edit/delete blog posts and upload images.

### 1) Configure admin session secret

Create / update `.env.local`:

```bash
ADMIN_SESSION_SECRET=your_long_random_secret
```

Restart the dev server after changing env vars.

Optional CLI (upsert admin user directly in DB):

```bash
npm run admin:user -- --id 2553019 --password 'Optyisthebest'
```

### 2) Open the admin UI

- Go to `http://localhost:3000/admin`
- If no admin exists yet, enter a new id/password and click **Create admin** (stored in DB table `admin_users`)
- If admin is already configured, sign in with that id/password

After authentication, the post list + image library will load.

### 3) Create a new post

- Click **New**
- Fill:
  - **Title** (auto-fills a slug if slug is empty)
  - **Slug** (URL path: `/blog/<slug>`)
  - **Date** (calendar input; stored as `YYYY-MM-DD`)
  - **Excerpt** (used on cards + SEO snippet)
  - **Tags** (comma separated)
  - **Cover image** (dropdown)
- Write the post in **Rich text body**

Click **Save**. Use **View** to open the published post in a new tab.

### Images (cover + inline)

Uploads are stored as “objects” in the local CMS SQLite database and served from:

- `/blog/images/<file>` (DB-first; falls back to `public/blog/images` / `content/blog/images` if present)

Optionally, uploads can also be mirrored to the filesystem (useful for local dev / static assets):

- `content/blog/images/*`
- `public/blog/images/*`

In the admin UI:

- Upload an image, then choose:
  - **OK** → set as the cover image for the post
  - **Cancel** → insert into the rich text body at the cursor
- Or insert from **Image library** (you can insert the same image multiple times)

### Where posts live

- Source-of-truth: SQLite (`CMS_DB_PATH`)
- Optional mirror: `content/blog/posts/*.md`
- Routes:
  - Index: `/blog`
  - Post: `/blog/[slug]`

### Notes / limitations

- The admin APIs and blog pages require a Node runtime (not a static export). They store content in SQLite (`CMS_DB_PATH`).
- The first time you open `/admin`, existing `content/blog/posts/*.md` will be seeded into SQLite automatically.
- For local authoring / Git-based workflows, set `CMS_WRITE_POSTS_TO_DISK=true` to mirror DB changes back into `content/blog/posts/*.md`.
- Image mirroring to disk is disabled by default. Set `CMS_WRITE_IMAGES_TO_DISK=true` only if you intentionally want local file mirrors (it’s best-effort; hosted environments may be read-only).
- Auth uses DB-stored credentials (`admin_users`) plus an HttpOnly session cookie. Treat this as “internal tooling”, not a hardened public CMS.
