Repository navigation
Conversation
Groundwork for letting committees write to the backend. A Collection names its own freetser table (one row per item), the permission that may read it, the permission that may write it, an item validator and a size cap. COLLECTION_TABLES joins DB_TABLES so each collection's table is created and reset like every other table. The registry starts empty; the first collection follows separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /members/collections/{list,save,delete}/ with {"collection": ...}
in the body. Access comes from the collection's registry entry: its
read role (or admin) may list, its write role (or admin) may save or
delete one item at a time. The handlers only touch the requested
collection's own table, so a committee permission can't write anything
else. The session check (a blocking auth call) runs before
store_queue.execute, per CLAUDE.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collection "fotos" in table collection_fotos: every member reads the cards, members with the focus tag (and admins) edit them. fotos.py checks a card's shape (name, YYYY-MM-DD date, event type, optional season, image data URL or web URL, up to 20 web links; no unknown fields), mirroring PhotoEvent in the frontend. Items are capped at 1 MB because cover images are stored inline after resizing in the browser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers the card validator, a FOCUS member creating, editing and deleting cards one at a time (the other cards stay untouched), admins editing without the tag, plain members and anonymous requests being refused, the focus tag unlocking no admin route, and invalid, oversized or unknown-collection requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New "Committee Collections" section: one table per collection, the registry, the security boundary (a committee tag only writes its own collection), the three routes and their errors, and how to add a collection for another committee. Adds collection_<name> to Core Tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jefryelb
force-pushed
the
feature/fotoboek-backend
branch
from
October 17, 2026 12:27
fde59cc to
754373a
Compare
The backend stores created_at (unix seconds) with every item: set on create, kept when the item is replaced. It is server-owned like id, so a created_at sent by the client is ignored. Pages can use it for things like a "Nieuw!" badge without anyone having to maintain it by hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the card in line with the current FOCUS photo overview in Notion: - end_date (optional): for events spanning several days, not before date - types: 1-3 different event types instead of one type - in_progress (optional bool): "Wordt aan gewerkt", FOCUS is still editing the photos The validator is split into small per-field checks. spec.md documents the server-owned created_at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
✅ Tested on demo (2026-10-17)Deployed to demo.dsavdodeka.nl together with #57, through a temporary branch that has since been deleted. The demo frontend was built from
Afterwards the server was restored. |
Collaborator
|
Didn't do full review but some suggestions:
Will add some more stuff later! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
New feature: committees can write to the backend. A member with a committee tag (e.g.
focus) can create, edit and delete data from that committee's own page.Security: a committee can only write to its own table, and to nothing else. The tag unlocks no other route and no other table.
First user: FOCUS managing the photo album on
/leden/fotos(Het Fotoboek).Admins don't configure anything; they only give a member the committee's tag in
/admin→ Users. Who may read and write is fixed in the code.Context
Until now, only admins could write anything to the backend. FOCUS needs to maintain Het Fotoboek, a member-only page with a card per event (cover image plus links to the photo albums). Two options existed, and neither was acceptable:
/admin/private_kv/*). It's admin-only, and it keeps all cards in one JSON document, so every save re-sends the whole list and one bad save can wipe every card. It is meant for small admin content likehordes_joinand stays that way. A save bug found there is fixed separately in Fix: saving an existing private key returned a 500 #57.So this PR adds a proper, narrow way for committees to write.
How it works
1. A committee gets its own table
Each piece of committee data is a collection: a list of items in its own table, one row per item. Collections are registered once in code, in
COLLECTIONS(data/features/committee_collections.py). This is the fotoboek's entry:The table is created automatically, because the collection tables are added to
DB_TABLES.2. Three routes, checked against that registry
The same three routes serve every collection; the body says which collection.
POST /members/collections/list/{collection}POST /members/collections/save/{collection, item}POST /members/collections/delete/{collection, id}This flowchart shows what happens when a FOCUS member saves a fotoboek card:
flowchart TD A["POST /members/collections/save/<br/>{collection: 'fotos', item}"] --> B{"'fotos' in<br/>COLLECTIONS?"} B -->|no| X1["404 unknown collection"] B -->|yes| C{"session has 'focus'<br/>or 'admin'?"} C -->|no| X2["403 Forbidden"] C -->|yes| D{"card passes<br/>validate_foto_card?"} D -->|no| X3["400 with the problems"] D -->|yes| E{"under 1 MB?"} E -->|no| X4["413 too large"] E -->|yes| OK["200: write this one row<br/>in collection_fotos"]3. The security boundary
This diagram shows everything a committee tag can reach:
flowchart LR F(["member with<br/>'focus' tag"]) -->|"save / delete"| T["collection_fotos<br/>(own table)"] F -.->|"403"| A["/admin/* routes"] F -.->|"403"| O["other committees'<br/>collections"] F -.->|"no route"| U["users, registrations,<br/>permissions, …"]write_role. FOCUS can writefotos, and nothing a different committee owns.javascript:image URLs and non-web links.focustag unlocks none of the admin routes (/admin/private_kv/set/,/admin/add_permission/,/admin/delete_user/).4. Saving one item at a time
Every save or delete touches exactly one row. Two FOCUS members editing different cards can't overwrite each other, and a failed page load can't wipe other cards. Edits to the same card are last-write-wins.
The backend also records when each item was created (
created_at). It sets it on create and keeps it on every later save, and a client can't set it. The fotoboek uses it for an automatic "Nieuw!" badge.5. The fotoboek card
data/features/fotos.pymirrors the current FOCUS photo overview in Notion:namedateYYYY-MM-DD, the (first) day of the eventend_datedatetypesWedstrijd,Gezelligheid,Training,ReisandOverig(e.g. Batavierenrace = Wedstrijd + Gezelligheid)in_progressseizoen25/26imagelinks{label, url}with web URLsUnknown fields are rejected.
6. Adding another committee later
COLLECTIONS.useCollection,useSaveCollectionItemanduseDeleteCollectionItem.No new routes or admin setup are needed.
Commits
The PR is split so each step can be reviewed on its own:
Collection, the empty registry, table operations, and the collection tables added toDB_TABLES.fotoscollection for FOCUS, plus the card validator indata/features/fotos.py.tests/test_committee_collections.py.spec.md, including the security boundary.created_at: the backend records when each item was created.end_date, severaltypesandin_progress, matching the Notion overview.Verification
uv run ty check,uv run ruff formatanduv run ruff checkpass on every commit.uv run pytest: 49 passed, 2 skipped.tests/test_committee_collections.py:created_at: the backend sets it, keeps it on edit, and ignores a client-sent value.feature/fotoboek-backendindodekafrontend):/leden/fotos.BACKEND_PRIVATE_LOCALHOST=1 FAROE_PRIVATE_HOST=127.0.0.1 uv run pytest.Not tested here: a deployed demo or production environment.
Rollout
feature/fotoboek-backendindodekafrontend, released vialeden_development).focustag expires a year after it's granted, like every tag. After that the member can't edit until an admin grants it again.🤖 Generated with Claude Code