Commit 7d9a607
authored
admin: CreateTable / DeleteTable write endpoints (P1, leader-only) (#634)
Stacked on #633 (the read-only chunk). Writes are limited to the leader
node for now; follower-side `AdminForward` RPC (design Section 3.3
acceptance criteria 1-6) ships in a follow-up PR. Mergeable on its own —
followers respond `503 leader_unavailable` + `Retry-After: 1`.
## Summary
- `POST /admin/api/v1/dynamo/tables` and `DELETE
/admin/api/v1/dynamo/tables/{name}` both go through the existing protect
chain (BodyLimit → SessionAuth → Audit → CSRF). The handler also
enforces `RoleFull` so a read-only key cannot create or delete even with
a valid CSRF token.
- Adapter side: `AdminCreateTable` / `AdminDeleteTable` take an
`AdminPrincipal` and re-validate the role at the adapter layer even when
a higher tier already enforced it. Preserves the design's *adapter side
is the source of truth for authz* invariant (Section 3.2). Two sentinel
errors (`ErrAdminNotLeader`, `ErrAdminForbidden`) signal the structured
failure modes.
- Bridge in `main_admin.go` translates adapter errors to admin sentinels
(`ErrTablesNotLeader` to 503 + `Retry-After: 1`, `ErrTablesForbidden` to
403, `ResourceInUse` to 409, `ResourceNotFound` to 404,
`ValidationException` to 400). Raw adapter error text is never surfaced
to clients; everything else falls through to a generic 500 with the
original message logged at error level.
- Strict JSON decoding (`DisallowUnknownFields`); each validation
message is plain English so the SPA can render it directly.
- Two summary structs (`adapter.AdminCreateTableInput` /
`admin.CreateTableRequest`) stay independent so neither package imports
the other; the bridge keeps them in sync and any drift breaks the build
there.
## Test plan
- [x] `go build ./...`
- [x] `go vet ./...`
- [x] `golangci-lint run` (admin, adapter, root: 0 issues)
- [x] `go test ./internal/admin/ -count=1` (49 tests pass — 14 new
write-handler unit tests, 4 new server-level integration tests)
- [x] `go test ./adapter/ -count=1 -run 'TestDynamoDB_Admin'` (14 tests
pass — 9 new write-path tests including duplicate rejection, role
enforcement at adapter, validation errors, delete missing to
ResourceNotFound, etc.)
- [ ] Manual smoke against a running node:
- `curl -X POST .../dynamo/tables` with full-role cookies + CSRF header
to 201 + JSON summary
- same against a follower to 503 + `Retry-After: 1`
- `DELETE` on a non-existent table to 404 `not_found`
## Stacked roadmap
1. **#633** read-only `GET /tables` + `GET /tables/{name}` (in review)
2. **THIS PR** — `POST` + `DELETE` (leader-only)
3. AdminForward RPC + follower-leader forwarding (Section 3.3 acceptance
criteria 1-6)
4. S3 read-only endpoints
5. S3 write endpoints
6. SPA (React + Vite, embed.FS)11 files changed
Lines changed: 3001 additions & 25 deletions
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
277 | 277 | | |
278 | 278 | | |
279 | 279 | | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
280 | 285 | | |
281 | 286 | | |
282 | 287 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
34 | 42 | | |
35 | 43 | | |
36 | 44 | | |
| |||
92 | 100 | | |
93 | 101 | | |
94 | 102 | | |
95 | | - | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
96 | 118 | | |
97 | 119 | | |
98 | 120 | | |
| |||
119 | 141 | | |
120 | 142 | | |
121 | 143 | | |
122 | | - | |
123 | | - | |
124 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
125 | 151 | | |
126 | 152 | | |
127 | 153 | | |
128 | 154 | | |
129 | 155 | | |
130 | | - | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
131 | 161 | | |
132 | 162 | | |
133 | 163 | | |
| |||
177 | 207 | | |
178 | 208 | | |
179 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
180 | 219 | | |
181 | 220 | | |
182 | | - | |
183 | | - | |
| 221 | + | |
| 222 | + | |
184 | 223 | | |
185 | | - | |
| 224 | + | |
186 | 225 | | |
187 | | - | |
| 226 | + | |
188 | 227 | | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
189 | 231 | | |
190 | 232 | | |
191 | 233 | | |
| |||
0 commit comments