# W·Dot CMS — Sprint 5 Backend Skeleton

> Senior-grade Laravel 11 baseline for W·Dot CMS, covering GATE 3 (Auth),
> GATE 4 (Compliance), GATE 5 (Multitenancy) from ADR-001.
>
> **Status**: skeleton authored locally because the target server
> (`112.162.17.116:22`) was unreachable from the dev environment
> (`Operation timed out`). All deployment commands below were drafted but
> **not executed against the server**.

---

## What's included

| Layer | Files |
|---|---|
| Composer / env | `composer.json`, `.env.example`, `phpunit.xml` |
| App bootstrap | `bootstrap/app.php`, `routes/api.php`, `routes/console.php` |
| Multitenant core (GATE 5) | `app/Models/Tenant.php`, `app/Models/Traits/BelongsToTenant.php`, `app/Http/Middleware/IdentifyTenant.php`, `app/Providers/TenancyServiceProvider.php` |
| Auth (GATE 3) | `app/Models/User.php`, `app/Models/UserIdentity.php`, `app/Http/Controllers/Auth/{Login,Register,OAuth}Controller.php`, `app/Services/Auth/TwoFactorService.php` |
| RBAC | `database/seeders/DefaultRolesSeeder.php` (5 roles × 24 perms) |
| Compliance (GATE 4) | `app/Models/{AuditLog,ConsentRecord}.php`, `app/Services/Compliance/{IsmsControls,AuditLogger}.php`, `app/Observers/AuditingObserver.php`, `app/Console/Commands/CompliancePiaReport.php`, `database/seeders/DefaultIsmsControlsSeeder.php` |
| Migrations | 9 files in `database/migrations/` — cache/jobs, tenants, users, user_identities, personal_access_tokens, audit_logs, consent_records, isms_controls, spatie/permission |
| Tests (Pest) | `tests/Feature/Auth/RegisterTest.php`, `tests/Feature/Tenancy/TenantIsolationTest.php`, `tests/Feature/Compliance/AuditLogTest.php` |

### Counts

- Models: **5** (Tenant, User, UserIdentity, AuditLog, ConsentRecord)
- Controllers: **4** (Controller base, Login, Register, OAuth)
- Migrations: **9**
- Seeders: **3** (DatabaseSeeder, DefaultRolesSeeder, DefaultIsmsControlsSeeder)
- Services: **3** (TwoFactorService, IsmsControls, AuditLogger)
- Pest tests: **3 files / 9 cases**

---

## Server deployment (run on `112.162.17.116`)

```bash
# 1. Upload skeleton
rsync -av --delete \
  /Users/wongyun_w/Desktop/easybox-w/files/wdot-cms/backend-skeleton/ \
  root@112.162.17.116:/www/wwwroot/wdot-v1/backend/

# 2. Bootstrap Laravel skeleton on the server, then merge our files
ssh root@112.162.17.116 <<'SH'
cd /www/wwwroot/wdot-v1/backend

# Install dependencies
composer install --no-interaction --prefer-dist --optimize-autoloader

# Env + key
[ -f .env ] || cp .env.example .env
php artisan key:generate --force

# DB
mysql -e "CREATE DATABASE IF NOT EXISTS wdot_v1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
php artisan migrate --force

# Seed
php artisan db:seed --class=DefaultRolesSeeder --force
php artisan db:seed --class=DefaultIsmsControlsSeeder --force

# Publish vendor configs
php artisan vendor:publish --provider="Laravel\\Sanctum\\SanctumServiceProvider" --tag=sanctum-config
php artisan vendor:publish --provider="Spatie\\Permission\\PermissionServiceProvider"

# Sanity
php artisan test
php artisan l5-swagger:generate
php artisan compliance:pia-report
SH
```

Once `php artisan test` is green on the server, create
`/www/wwwroot/wdot-v1/backend/SPRINT5_DONE.md` and commit.

---

## Local quick start

```bash
cd /Users/wongyun_w/Desktop/easybox-w/files/wdot-cms/backend-skeleton
composer install
cp .env.example .env
php artisan key:generate
# SQLite for local dev — phpunit.xml already uses :memory:
touch database/database.sqlite
DB_CONNECTION=sqlite DB_DATABASE=$(pwd)/database/database.sqlite \
  php artisan migrate --seed
./vendor/bin/pest
```

---

## Open issues / TODO before merge

1. **Server unreachable** — SSH to `112.162.17.116:22` timed out from dev box.
   Verify firewall / SSH key on the server before re-running deploy script.
2. **OAuth providers** — `OAuthController::exchangeCodeForProfile()` is a stub.
   Implement real Kakao / Naver / Google / Apple token exchange in Sprint 5 W2.
3. **MeiliSearch + Horizon configs** not yet published (intentional — done at deploy).
4. **`spatie/laravel-multitenancy`** is listed in `composer.json` but the
   skeleton uses the lightweight in-house `BelongsToTenant` trait. The package
   is reserved for Sprint 7+ when row-level isolation needs DB switching.
5. **L5-Swagger annotations** present on Auth controllers only; expand to
   Members/Pages/Blocks endpoints in Sprint 6.
6. **EGov security-headers middleware** not yet emitted — scheduled for Sprint 5 W3.

---

## ADR references

- GATE 3 (회원 식별) — `_decisions/ADR-001-decision-gates.md` §GATE 3
- GATE 4 (컴플라이언스) — §GATE 4
- GATE 5 (멀티테넌트) — §GATE 5
- API contract — `docs/03_implementation/06_REST_API_OPENAPI.md`
