# ASIM Scheduling System

Church volunteer scheduling system (ASIM = Adik Sacristan Indonesia Muda).

## Tech Stack

- **Frontend:** React 19 + TypeScript + Umi.js v4 + Ant Design Pro + Ant Design v6
- **Backend:** Python 3.12 + FastAPI + SQLAlchemy + PostgreSQL
- **Auth:** JWT (HS256, 24h expiry) + bcrypt + role-based access (asim / pengurus / super_admin)

## Project Structure

```
/                        # Project root
├── frontend/            # React app (Umi.js v4)
│   ├── config/          # Umi config: routes.ts, config.ts, proxy.ts
│   ├── src/pages/       # Route components (auto-routed by Umi)
│   ├── src/services/asim/api.ts  # Axios API client (BASE_URL: localhost:8000)
│   ├── src/locales/     # i18n (en-US primary, id-ID)
│   ├── src/access.ts    # Role-based menu/route access
│   └── src/config/constants.ts   # BASE_URL, DAY_LABELS
├── backend/             # FastAPI app
│   ├── app/main.py      # App entry + router registration + auth dependencies
│   ├── app/core/security.py  # JWT, password hashing, auth guards
│   ├── app/core/limiter.py   # slowapi rate limiter
│   ├── app/models/      # SQLAlchemy ORM models
│   ├── app/routes/      # FastAPI endpoints
│   ├── app/schemas/     # Pydantic request/response models
│   └── app/helpers/     # Utility functions (crud.py)
└── ignore/              # Ignore this folder entirely
```

Feature-specific docs: see `backend/CLAUDE.md` and `frontend/CLAUDE.md`.

## Development Commands

### Frontend (from `frontend/`)
```bash
env PORT=8001 npm run start:dev   # Dev server without mock (→ localhost:8001)
npm run build                      # Production build (writes to dist/)
npm run lint                       # Biome lint + TypeScript check
```

**PENTING — dev server vs build:**
- **JANGAN jalankan `npm run build` sementara dev server sedang jalan** — akan menyebabkan `Uncaught SyntaxError: Unexpected token '<'`
- Node binary: `/opt/homebrew/opt/node/bin/node` — harus set `PATH="/opt/homebrew/opt/node/bin:$PATH"`

### Backend (from `backend/`)
```bash
source venv/bin/activate
python -m uvicorn app.main:app --reload --port 8000
```

### Environment Variables
- **Backend `.env`:** `DATABASE_URL`, `SECRET_KEY`
- **Frontend:** API base URL hardcoded in `src/config/constants.ts` → `http://localhost:8000`

## RBAC Roles
| Role         | Frontend Access   | Backend Access               |
|-------------|-------------------|------------------------------|
| asim        | Portal pages only | Read own data, shift swaps   |
| pengurus    | All admin pages   | Full CRUD on all resources   |
| super_admin | + Settings page   | + User management            |

## Code Style

- **Frontend linting:** Biome (enforced via Husky pre-commit)
- **Commit messages:** Conventional Commits (`feat:`, `fix:`, `docs:`, etc.)
- **Language:** Indonesian for UI labels, English for code/comments
- **Package manager:** npm (Node >= 20)
- **Python:** 3.12, no Alembic migrations (schema in models)

## Git Repository

- Remote: `https://github.com/dsentosa/asim`
- Branch: `main`
- `frontend/dist/` di-track oleh git — selalu build + commit sebelum deploy

### GitHub Push (tidak ada credentials di local)
```bash
git bundle create /tmp/asim-push.bundle HEAD main
rsync -av -e "ssh -i ~/.ssh/me_goteku_deploy" /tmp/asim-push.bundle root@goteku.com:/tmp/asim-push.bundle
ssh -i ~/.ssh/me_goteku_deploy root@goteku.com "
  rm -rf /tmp/asim-bundle-repo
  git clone /tmp/asim-push.bundle /tmp/asim-bundle-repo
  cd /tmp/asim-bundle-repo
  git remote set-url origin git@github.com:dsentosa/asim.git
  git push origin main --force
"
```

## Deploy ke Live Server

Live server path: `/var/www/html/asim.com/`
Backend process: dikelola oleh systemd — restart otomatis setelah `git pull`.

```bash
# 1. Build frontend
cd frontend && PATH="/opt/homebrew/opt/node/bin:$PATH" npm run build && cd ..

# 2. Commit (termasuk dist/)
git add -A && git commit -m "feat/fix: ..."

# 3. Push ke GitHub via bundle (lihat section di atas)

# 4. Pull di server
ssh -i ~/.ssh/me_goteku_deploy root@goteku.com "
  cd /var/www/html/asim.com && git pull origin main
"
```

## Common Gotchas

1. Umi dev server auto-picks available port (config says 8001, may start on 8005)
2. Backend CORS allows `localhost:3000`, `localhost:8000–8005`, and `http://asim.goteku.com` — update `main.py` if port changes
3. slowapi parameter MUST be named `request: Request` even though rate limit removed from login
4. Portal ASIM routes use JWT-derived identity (not query params) for ownership checks
5. `src/.umi/` is auto-generated — never edit manually
6. `requester_id` on `shift_swaps` is nullable — always null-check before using
7. Same-week hard rule uses `get_week_start()` — never use raw `diff <= 7` for week check
8. `_load_schedule_cache()` must be called before any `enrich_swap()` batch — never call `get_schedule_info()` inside a loop
9. `frontend/node_modules/` = 1.6 GB — normal, excluded from git
10. Admin login: `admin@asim.com` (live password unknown locally — reset jika perlu)
11. Dev mode detection di frontend: pakai `window.location.hostname === 'localhost'` — BUKAN `process.env.NODE_ENV`
12. `test_date` param di `/api/attendance/my-schedules` hanya aktif kalau `development_mode = "true"` di tabel `system_settings`
13. `exportStatic: {}` in `config.ts` — Umi generates static HTML for every route
