# Baskit Ops

The central backend and operations console for Baskit — the hub the customer
and picker apps, the price scraper, the payment gateway, and the operations
team all talk to.

Built on **CodeIgniter 4 + Shield** (authentication, users, permissions),
running against **PostgreSQL** in containers, deployable to **Azure Container
Apps** via the GitHub Actions pipeline.

> **Status:** Domains through Reports are live (see `docs/BUILD_LOG.md`). The
> admin **Dashboard** shows live open-order KPIs plus a Constantia ops map
> (Leaflet) with store / delivery / pickup / picker layers.

## Stack

| Concern | Choice |
|---|---|
| Framework | CodeIgniter 4.7 |
| Auth / users / permissions | CodeIgniter Shield 1.3 |
| Database | PostgreSQL 16 (Docker) |
| Settings store | codeigniter4/settings |
| Console UI | Server-rendered CI4 views (Silicon Overdrive CI) |
| Deploy target | Azure Container Apps |

## Local development

Requires Docker Desktop. Postgres runs in a container; the app runs the same
image used in production with source bind-mounted for live editing.

```bash
# Start Postgres + app (migrations run automatically on the app's first boot)
docker compose up -d

# Seed the first console account (owner)
docker compose run --rm -e RUN_MIGRATIONS=false app php spark db:seed OwnerSeeder

# Optional: DB browser at http://localhost:8081
docker compose --profile tools up -d adminer
```

Then open **http://localhost:8080/** and sign in:

- **owner@baskit.co.za** / **ChangeMe#2026** — change it immediately under *My profile*.

Override the seeded credentials with `BASKIT_OWNER_EMAIL` / `BASKIT_OWNER_PASSWORD`.

### Migrations

Everything is migrated — no manual schema changes.

```bash
docker compose run --rm -e RUN_MIGRATIONS=false app php spark migrate --all
docker compose run --rm -e RUN_MIGRATIONS=false app php spark migrate:rollback
```

## Authorization model

Roles + a per-user override matrix. One resolver — `App\Libraries\ConsoleAccess`
— is the single gate the navigation, route filters, and views all check through.

- **Groups** (`Config\AuthGroups::$groups`) are the roles: `owner`, `ops`,
  `support`, `finance`, `picker-manager` (console) plus `customer`, `picker`
  (app-only — they can authenticate but never reach `/admin`).
- **Permissions** (`$permissions`) are per-module capabilities like
  `orders.refund`, `users.permissions`.
- **The matrix** (`$matrix`) sets each role's default capabilities.
- **Per-user overrides** (`console_permission_overrides` table) refine an
  individual user: `allow` grants a capability the role lacks; `deny` removes
  one the role grants. **Deny always wins.** Edited at
  `/admin/users/{id}/permissions`.

Route gating: `['filter' => 'consoleauth']` guards the whole console;
`['filter' => 'perm:orders.view']` gates a route to a capability (comma-separate
for OR).

Every console mutation is written to `audit_logs` (`App\Libraries\Audit`) with
who / what / when / before / after — viewable at `/admin/users/audit`.

## Deployment (Azure Container Apps)

`.github/workflows/deploy.yml` builds the image, pushes it to Azure Container
Registry, runs migrations as a one-off Container Apps Job, then rolls out a new
web revision. Configure these repository secrets:

`AZURE_CREDENTIALS`, `ACR_NAME`, `ACA_RESOURCE_GROUP`, `ACA_APP_NAME`,
`ACA_MIGRATE_JOB`.

On the Container App, set application config as env/secrets (never in the repo):

```
CI_ENVIRONMENT=production
RUN_MIGRATIONS=false                 # migrations run via the pipeline job
app.baseURL=https://ops.baskit.co.za/
app.forceGlobalSecureRequests=true   # force HTTPS
encryption.key=hex2bin:...           # generate with `php spark key:generate`
database.default.hostname=...        # managed Postgres
database.default.database=...
database.default.username=...
database.default.password=...        # secret
database.default.port=5432
```

## Security

- Shield sessions for the console; bcrypt password hashing; login throttling.
- CSRF protection on all browser routes (`api/*`, `ingest/*` excepted for the
  future stateless JWT/service-key surfaces).
- Secure response headers, `invalidchars`, and forced HTTPS in production.
- App-only accounts cannot reach the console (`console.access` guard).
- Audit log on every console write. POPIA-aligned data handling.

## Repository layout

```
app/
  Config/            AuthGroups (RBAC), Auth, Database, Filters, Routes, Services
  Controllers/Admin/ Dashboard, Users, Settings, Profile, Module (stubs)
  Filters/           ConsoleGuardFilter, PermissionFilter
  Libraries/         ConsoleAccess (resolver), Audit
  Models/            ConsolePermissionOverrideModel, AuditLogModel
  Database/          Migrations, Seeds
  Views/             layouts/console, admin/*, errors/html/error_403
docker/              Dockerfile assets (php ini, entrypoint)
.github/workflows/   deploy.yml
docs/                Baskit_PRD_Backend.html
```
