DOCUMENTATION
Quickstart Guide
Get the full Reiwa Sakura Tech platform running in under 5 minutes. Everything you need — from local dev to production.
Prerequisites
- Node.js 20+ and npm 10+
- PHP 8.2+ with
pdo_pgsql,openssl,mbstringextensions - PostgreSQL 14+ (local or Docker)
- Composer 2+
- Docker 24+ (optional — only needed for the containerized workflow)
1. Clone & Install
Clone the monorepo and install all portal and API dependencies.
git clone https://gitlab.reiwasakura.tech/Stephan_Filip/reiwasakura.tech cd reiwasakura.tech # Install Node dependencies for all 6 portals npm install # Install PHP dependencies for the API cd api && composer install && cd ..
npm workspaces are not used here — each portal has its own package.json.
The root npm install runs install across all workspaces via the root package.json scripts.
2. Database Setup
Create the PostgreSQL database, apply the schema, and load seed data.
# Create the database createdb reiwasakura_dev # Apply the full schema (all 12 tables + indexes + triggers) psql reiwasakura_dev < database/schema.sql # Load dev seed data (1 super_admin, sample projects, jobs, blog posts) psql reiwasakura_dev < database/seed.sql
admin@reiwasakura.tech / Admin@1234567
Change this immediately on any non-local environment.
3. Environment Variables
Copy the example env file and fill in your values. The API reads from api/.env; each portal reads its own .env (or .env.local).
cp api/.env.example api/.env nano api/.env
# Database DB_HOST=127.0.0.1 DB_PORT=5432 DB_NAME=reiwasakura_dev DB_USER=postgres DB_PASSWORD=your-db-password # JWT — must be at least 32 random characters JWT_SECRET=change-me-to-a-long-random-string-32chars+ # Mail (Poste.io or any SMTP) MAIL_HOST=mail.reiwasakura.tech MAIL_PORT=587 MAIL_USER=noreply@reiwasakura.tech MAIL_PASS=your-smtp-password MAIL_FROM=Reiwa Sakura Tech # CORS — comma-separated list of allowed origins CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002,http://localhost:3003,http://localhost:3004,http://localhost:3005 # Portal base URLs (used for cross-portal redirects) PUBLIC_URL_FRONTEND=http://localhost:3000 PUBLIC_URL_AUTH=http://localhost:3004 PUBLIC_URL_ADMIN=http://localhost:3001 PUBLIC_URL_DOCS=http://localhost:3005 # API URL — keep as /api for relative proxy path PUBLIC_API_URL=/api
4. Start Dev
Use the CLI agent to start all 7 services with a single command, or start them individually.
# Start everything at once (recommended) node cli-agent.js dev # Or start a single portal cd frontend && npm run dev # :3000 cd admin && npm run dev # :3001 cd career && npm run dev # :3002 cd developer && npm run dev # :3003 cd auth && npm run dev # :3004 cd docs && npm run dev # :3005 # Start the PHP API (runs via Docker or php -S) cd api && php -S 0.0.0.0:3006 index.php
Alternatively, use docker compose up -d from the repo root to start everything containerised.
The PHP API listens on 0.0.0.0:3006 inside the container, bound to 127.0.0.1:3006 on the host.
| Service | URL | Purpose |
|---|---|---|
| Frontend | :3000 | Public website |
| Admin | :3001 | CMS — users, projects, jobs, blog |
| Career | :3002 | Job listings & applications |
| Developer | :3003 | Kanban board, time tracking |
| Auth | :3004 | Login, logout, OTP flows |
| Docs | :3005 | This documentation site |
| API | :3006 | PHP REST API (internal) |
What's next?
JWT Authentication
Every protected endpoint requires a Bearer JWT. The token is issued by
POST /api/auth/login and stored in localStorage under the key rst_token
as a JSON object — not as a raw string.
// What's stored in localStorage['rst_token']
interface Session {
token: string; // Signed JWT (HS256, 1 h TTL)
refresh?: string; // Refresh token (7 day TTL) for silent rotation
exp: number; // Epoch ms — when the access token expires
}
// Read the token safely
const raw = localStorage.getItem('rst_token');
const sess: Session = JSON.parse(raw);
const jwt = sess.token; {
"user_id": 42,
"email": "alice@reiwasakura.tech",
"first_name": "Alice",
"last_name": "Chen",
"role": "developer",
"iat": 1750000000,
"exp": 1750003600
} // Login — store the returned session
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'alice@reiwasakura.tech', password: '...' }),
});
const { data } = await res.json();
// data.access_token, data.refresh_token, data.expires_in
localStorage.setItem('rst_token', JSON.stringify({
token: data.access_token,
refresh: data.refresh_token,
exp: Date.now() + data.expires_in * 1000,
}));
// Authenticated request
const { data: tasks } = await fetch('/api/tasks', {
headers: { Authorization: `Bearer ${JSON.parse(localStorage.getItem('rst_token')).token}` },
}).then(r => r.json()); @reiwasakura.tech email addresses can sign in.
Passwords must be at least 12 characters.
The presence cookie rst_logged_in=1 is set on .reiwasakura.tech as a UI hint only — the JWT is never stored in a cookie.
Each portal schedules a silent refresh 4 minutes before the access token expires. The refresh token is rotated on every use (refresh token rotation).
// POST /api/auth/refresh
const r = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: sess.refresh }),
});
const { data } = await r.json();
// data.access_token — new JWT
// data.refresh_token — new refresh token (old one is now invalid)
// data.expires_in — seconds until new JWT expires Theme System
All portals share a CSS custom property theme system defined in public/styles/theme.css.
Toggle dark/light by calling window.__toggleTheme() — it writes to localStorage and
updates the data-theme attribute on <html>.
/* Light mode — applied when data-theme="light" */
:root {
--bg: #faf8ff;
--text: #0d0b14;
--accent: #7c3aed;
--surface: #ffffff;
--surface-2: #f3f0ff;
--border: rgba(100,80,180,.12);
}
/* Dark mode — applied when data-theme="dark" */
[data-theme="dark"] {
--bg: #0d0b14;
--text: #e8e0ff;
--accent: #a96ef5;
--surface: #130f1e;
--surface-2: #1a1528;
--border: rgba(255,255,255,.08);
}
/* Accent colours available everywhere */
--purple: #a96ef5;
--pink: #e8788a;
--teal: #4ecdc4;
--gold: #f7c948;
--green: #3ddc84;
--red: #ff6363; Layout.astro — no flash of unstyled content.
The ThemeToggle component uses window.__toggleTheme() internally and flips [data-theme-icon] display attributes.