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
+3 -1
View File
@@ -12,7 +12,7 @@
<td>{{ item.contact }}</td>
<td>{% if item.repo_url %}<a href="{{ item.repo_url }}" rel="nofollow noopener" target="_blank">{{ item.repo_url }}</a>{% if item.link_status %}<br><span class="muted">{{ item.link_status }}{% if item.link_title %}: {{ item.link_title }}{% endif %}</span>{% endif %}{% else %}<span class="muted">none</span>{% endif %}</td>
<td>{{ item.message }}</td>
<td><span class="status {{ item.status }}">{{ item.status }}</span></td>
<td><span class="status {{ item.status }}">{{ item.status }}</span>{% if item.invite_open %}<br><span class="muted">invite until {{ local_dt(item.invite_expires_at) }}</span>{% endif %}</td>
<td>{{ local_dt(item.created_at) }}</td>
<td class="actions">
<form method="post" action="/admin/joins/{{ item.uid }}/status">
@@ -20,6 +20,8 @@
<button class="button ghost small" type="submit">Set</button>
</form>
{% if item.repo_url %}<form method="post" action="/admin/joins/{{ item.uid }}/check"><button class="button ghost small" type="submit" data-loading>Check link</button></form>{% endif %}
{% if item.invite_open %}<form method="post" action="/admin/joins/{{ item.uid }}/invite/revoke"><button class="button ghost small" type="submit">Revoke invite</button></form>
{% elif item.status != 'declined' %}<form method="post" action="/admin/joins/{{ item.uid }}/invite"><button class="button ghost small" type="submit" data-loading>Issue invite</button></form>{% endif %}
</td>
</tr>
{% else %}