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
+6 -1
View File
@@ -99,7 +99,8 @@ def sync_gallery():
seen_hashes.append(phash)
thumb_directory = existing.get("thumb_directory") if existing else None
thumb_name = existing.get("thumb_name") if existing else None
if not thumb_directory or not blob_path(thumb_directory, thumb_name).exists():
changed = not existing or existing.get("phash") != str(phash)
if changed or not thumb_directory or not blob_path(thumb_directory, thumb_name).exists():
thumb_directory, thumb_name = write_blob(
existing["uid"] if existing else generate_uid(), "webp", _thumbnail_bytes(image)
)
@@ -108,6 +109,10 @@ def sync_gallery():
return synced
def missing_sources():
return [filename for filename, _, _ in GALLERY if not (config.MEDIA_SOURCE_DIR / filename).exists()]
def gallery_items(kind):
items = []
for row in media_by_kind(kind):