# {{projectName}} / {{appName}}

Voltro API scaffold (template: **api-backend-deactivation**) — the
`deactivation()` schema mixin: lock a user out without hiding their data.

## Boot

```bash
pnpm install
pnpm --filter @{{projectName}}/{{appName}} dev
# → http://localhost:4000  (store: memory — zero infra)
```

## What this shows

- **`deactivation()` mixin** — `users.with(deactivation())` adds
  `deactivatedAt` + `deactivatedBy` (→ `actors`) and pulls `audit()`
  transitively. It's a pure schema mixin — no `plugins[]` entry.
- **Visible-but-locked-out** — set `deactivatedAt` with a normal
  `ctx.store.update`. The row STAYS in the table and remains queryable; the
  convention is that your auth layer refuses to authenticate a subject whose
  `deactivatedAt` is set. This is the deliberate **opposite of
  `softDelete()`**, which hides + anonymises the row (its `defaultWhere`
  filters deleted rows out of every read).

## Try it

```bash
# create a user, then read it (deactivatedAt is null):
ID=$(curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
  -d '{"tag":"users.create","input":{"email":"ada@acme.com","name":"Ada"}}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["id"])')
curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
  -d "{\"tag\":\"users.get\",\"input\":{\"id\":\"$ID\"}}"
# → { ok:true, result:{ …, deactivatedAt:null } }

# deactivate, then read AGAIN — still there, now stamped:
curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
  -d "{\"tag\":\"users.deactivate\",\"input\":{\"id\":\"$ID\"}}" >/dev/null
curl -s localhost:4000/_voltro/inspect/invoke -H 'content-type: application/json' \
  -d "{\"tag\":\"users.get\",\"input\":{\"id\":\"$ID\"}}"
# → { ok:true, result:{ …, deactivatedAt:"2026-…" } }   (NOT null → still visible)
```

## Full user lifecycle

Compose the lifecycle mixins for the complete column set:
`users.with(audit(), softDelete(), deactivation())` — who/when (audit),
hidden+anonymised on delete (softDelete), and visible-but-locked-out
(deactivation).
