Member invites, gallery resync, content and operator docs

- Invite flow: admin issue/revoke on join requests, public single-use
  claim links (hash-only tokens, 7-day expiry, no state reveal), claim
  creates the Member account and marks the request accepted
- Gallery: admin status page plus resync endpoint; sync refreshes
  thumbnails whose content changed; tools/gallery contract and checker
- Docs: public content page, admin-only operator runbook, api.md
  invite/gallery sections, all routes in the live API docs, docs
  reachability gate test
- Screenshots cover the new pages; version 1.0.18
This commit is contained in:
2026-10-06 02:56:08 +02:00
parent e512556703
commit 9fb4d65787
36 changed files with 1147 additions and 19 deletions
+5 -1
View File
@@ -1,8 +1,12 @@
# retoor <retoor@molodetz.nl>
import re
USERNAME_PATTERN = re.compile(r"^[A-Za-z0-9_-]{3,32}$")
def test_people_lists_real_accounts_only(anon):
people = anon.get("/people", headers={"Accept": "application/json"}).json()
names = [person["username"] for person in people.get("people", [])]
assert names[0] == "retoor"
assert set(names) <= {"retoor", "lezer"}
assert {"retoor", "lezer"} <= set(names)
assert all(USERNAME_PATTERN.match(name) for name in names)