Files
Randify.pro/AGENTS.md
T

95 lines
5.8 KiB
Markdown

# Randify.pro — Knowledge Base
**Stack:** Astro 4 + Tailwind CSS 4 + TypeScript + PostgreSQL (Drizzle ORM)
**Deploy:** Docker Compose (app + postgres) on VPS, nginx reverse proxy, Let's Encrypt SSL
## Overview
Bilingual (EN/RU) static/hybrid site hosting random-value generators (10 live). Recently expanded with **DM Dashboard** — a server-rendered OAuth-authenticated subdomain (`/dm/`) with dice roller, initiative tracker, Open5e reference, and notes.
## Structure
```
/
├── src/
│ ├── pages/ # Astro pages (EN + RU mirrors)
│ │ ├── api/auth/ # OAuth 2.1 + PKCE (VK ID + Yandex)
│ │ ├── generators/ # Static generator pages
│ │ ├── dm/ # DM Dashboard (server-rendered, prerender=false)
│ │ └── ru/ # Russian mirror pages
│ ├── components/
│ │ ├── generators/ # Interactive generator components (see sub-AGENTS)
│ │ └── dm/ # DM Dashboard UI (see sub-AGENTS)
│ ├── layouts/ # BaseLayout, GeneratorLayout, DmLayout
│ ├── lib/
│ │ ├── auth/ # JWT, OAuth configs, session utils
│ │ ├── client/ # Browser utilities (dice, animations, random)
│ │ └── open5e/ # Open5e API client + cache
│ ├── db/ # Drizzle schema, migrations, client
│ ├── i18n/ # Translations (en/ru) + DM translations
│ ├── content/ # Astro Content Collections (generators, blog)
│ └── data/ # Static data (generator list, config)
├── tests/ # Vitest + Playwright tests
├── drizzle/ # SQL migrations
├── docker-compose.yml # App + PostgreSQL production setup
└── .github/workflows/ # CI/CD deploy via rsync
```
## Where to Look
| Task | Location | Notes |
|------|----------|-------|
| Add new generator | `src/content/generators/`, `src/components/generators/`, `src/pages/generators/` | Must create EN + RU simultaneously |
| Fix OAuth | `src/pages/api/auth/`, `src/lib/auth/`, `src/middleware/index.ts` | VK ID uses `id.vk.ru`, Yandex uses `oauth.yandex.com` |
| DB schema change | `src/db/schema.ts` → `drizzle/` | Run `npm run db:generate` then migrate |
| DM Dashboard UI | `src/components/dm/`, `src/pages/dm/` | Orange theme `#E87722`, RU-only |
| i18n strings | `src/i18n/translations.ts`, `src/i18n/dm-translations.ts` | `useT(lang)` returns typed object |
| Production deploy | `.github/workflows/deploy.yml`, `docker-compose.yml` | GitHub Actions → rsync → Docker Compose restart |
## Conventions
- **Path aliases:** `@/*` → `src/*` used everywhere
- **i18n routing:** Astro built-in. `Astro.currentLocale` drives locale. EN pages at root, RU under `/ru/`
- **Generator pages:** Each generator has its own `.astro` page file (never dynamic `[slug].astro` for generators — Astro bundles all imported scripts)
- **Color system:** `var(--accent)` in BaseLayout = `#534AB7` (purple). DM Dashboard overrides to `#E87722` (orange) via `.dm-theme` CSS class
- **Auth cookie name:** `auth_token` (unified across OAuth callback, middleware, logout)
- **DB connection:** `process.env.DATABASE_URL` — must match docker-compose `postgres` service hostname inside containers
## Anti-Patterns
- **Dynamic `[slug].astro` for generators:** Forbidden. Each generator must have its own page file to avoid script bundling issues
- **Using `url.origin` in OAuth callbacks:** Broken behind nginx proxy. Always use `process.env.PUBLIC_APP_URL || url.origin`
- **Joining multiple `Set-Cookie` with comma:** Browsers reject this. Use `Headers.append()` for each cookie
- **`SameSite=Strict` on session cookie:** Breaks cross-site OAuth redirects. Use `SameSite=Lax` + `Secure`
- **Static `prerender` for DM pages:** Middleware won't run, `Astro.locals.user` stays `null`. DM pages MUST have `export const prerender = false`
- **`Math.random()` for cryptographic purposes:** Generators should use `crypto.getRandomValues()` via `src/lib/client/random.ts`
- **Importing `import.meta.env` in server code:** Use `process.env` for server-side secrets (OAuth, JWT, DB)
- **Unknown fields in generator JSON:** `src/lib/generator-schema.ts` uses Zod `.strict()`. Any unknown field breaks the build
- **Unescaped `innerHTML` with user input:** Generators use `innerHTML` extensively. Always escape user input via `escapeHtml()` (currently duplicated in 3 files — consolidate to `src/lib/client/`)
- **OAuth empty-string fallbacks:** `process.env.VK_CLIENT_ID || ''` silently produces invalid requests. Validate env vars explicitly or fail fast
- **JWT secret non-null assertion:** `process.env.JWT_SECRET!` in `src/lib/auth/jwt.ts` crashes at runtime if env var is missing
## Commands
```bash
npm run dev # Dev server (localhost:4321)
npm run build # Static + server build → dist/
npm run preview # Preview built dist/
npm run test # Vitest unit tests
npm run test:e2e # Playwright E2E tests
npm run db:generate # Generate Drizzle migration
npm run db:migrate # Run migrations
```
## Notes
- **No test runner or linter** configured in CI (per CLAUDE.md)
- **Health endpoint** (`/api/health`) checks DB connectivity via `SELECT 1`
- **Analytics:** Yandex Metrika + Top.Mail.Ru counters, IDs in `src/data/config.ts`
- **Service Worker** registered in BaseLayout for PWA support
- **Blog** uses Astro Content Collections with MDX/Markdown
- **Dockerfile** is single-stage Node 20 Alpine (not multi-stage as CLAUDE.md claims)
- **ESLint override:** `public/sw.js` has `@typescript-eslint/no-unused-vars` disabled for service worker globals
- **Zero TODO/FIXME/HACK markers** in source code — codebase is clean
- **Explicit prerender required:** Hybrid output mode means every `.astro` page MUST declare `export const prerender = true/false`