Blame
|
1 | --- |
||||||
| 2 | category: reference |
|||||||
| 3 | tags: |
|||||||
| 4 | - P2 |
|||||||
| 5 | - dynamodb |
|||||||
| 6 | - infrastructure |
|||||||
| 7 | last_updated: 2026-03-13 |
|||||||
| 8 | --- |
|||||||
| 9 | ||||||||
| 10 | # P2-1: DynamoDB Tables — Summary |
|||||||
| 11 | ||||||||
| 12 | ## Status: Complete |
|||||||
| 13 | ||||||||
| 14 | ## Acceptance Criteria |
|||||||
| 15 | ||||||||
| 16 | - [x] Users table: create, read, update by ID; lookup by (oauth_provider, oauth_provider_sub) |
|||||||
| 17 | - [x] Wikis table: create, read, update, delete by (owner_id, wiki_slug); list by owner |
|||||||
| 18 | - [x] ACLs table: create, read, delete by (wiki_id, grantee_id); list by wiki |
|||||||
| 19 | - [x] PITR enabled on all tables |
|||||||
| 20 | - [x] Unit tests pass with moto (22/22) |
|||||||
| 21 | - [ ] Integration test: CRUD against real DynamoDB (not run — `pulumi up` deferred per task spec) |
|||||||
| 22 | ||||||||
| 23 | ## Files Changed |
|||||||
| 24 | ||||||||
| 25 | - `infra/components/dynamodb.py` — Pulumi `DynamoDbComponent` with 3 tables + GSI |
|||||||
| 26 | - `infra/__main__.py` — import + instantiation appended at end (no conflicts with P1-5/P1-6) |
|||||||
| 27 | - `app/models/__init__.py` — package init, re-exports all models |
|||||||
| 28 | - `app/models/user.py` — `UserModel` (create, get, get_by_oauth, update) |
|||||||
| 29 | - `app/models/wiki.py` — `WikiModel` (create, get, update, delete, list_by_owner) |
|||||||
| 30 | - `app/models/acl.py` — `AclModel` (create, get, delete, list_by_wiki) |
|||||||
| 31 | - `tests/test_dynamodb.py` — 22 unit tests with moto |
|||||||
| 32 | ||||||||
| 33 | ## Design Decisions |
|||||||
| 34 | ||||||||
| 35 | 1. **Table keys follow the data model exactly.** Users PK=id, Wikis PK=owner_id+SK=wiki_slug, ACLs PK=wiki_id+SK=grantee_id. |
|||||||
| 36 | 2. **Single GSI on Users** for `(oauth_provider, oauth_provider_sub)` login lookup. No GSI needed on Wikis (listing by owner is a native PK query) or ACLs (listing by wiki is a native PK query). |
|||||||
| 37 | 3. **ACL wiki_id is a composite string** (`owner_id:wiki_slug`), matching the data model comment "owner_id + wiki_slug". |
|||||||
| 38 | 4. **ACL create is an upsert** — re-granting a role overwrites the previous entry (no conditional write). This is intentional for role changes. |
|||||||
| 39 | 5. **Wiki and User creates use conditional writes** to prevent duplicates. |
|||||||
| 40 | 6. **PAY_PER_REQUEST billing** for dev (no capacity planning needed). |
|||||||
| 41 | 7. **All resources tagged** with `project: wikibot-io`, `environment: dev`. |
|||||||
| 42 | 8. **Plain boto3, no ORM** — models accept a `dynamodb_resource` parameter for dependency injection (used by moto in tests). |
|||||||
| 43 | ||||||||
| 44 | ## Test Results |
|||||||
| 45 | ||||||||
| 46 | ``` |
|||||||
| 47 | 22 passed in 15.19s |
|||||||
| 48 | ``` |
|||||||
| 49 | ||||||||
| 50 | All CRUD operations, GSI queries, conditional writes, error cases, and upsert behavior verified. |
|||||||
| 51 | ||||||||
| 52 | ## Pulumi Preview |
|||||||
| 53 | ||||||||
| 54 | Preview shows 3 new DynamoDB tables + 1 component resource, no conflicts with existing infrastructure. |
|||||||
| 55 | ||||||||
| 56 | ## Branch |
|||||||
| 57 | ||||||||
| 58 | `feat/P2-1-dynamodb` — 1 commit, not pushed. |
|||||||