feat: separate manifest.displayLabel for catalog UI from manifest.description
manifest.description is for composer / dev consumption (often a long English text). The admin catalog needs short Hebrew labels. Introduces optional manifest.displayLabel field — only that field is synced into extensions.description (the column the admin UI reads). When displayLabel is missing the DB row is left untouched, so curated labels never get clobbered by long English manifest descriptions. Adds POST /admin/set-labels for bulk backfilling labels in one call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-12
@@ -171,10 +171,12 @@ def sync_extension_registry():
|
|||||||
) sub WHERE e.id = sub.extension_id AND (e.latest_version IS NULL OR e.latest_version != sub.max_ver)""",
|
) sub WHERE e.id = sub.extension_id AND (e.latest_version IS NULL OR e.latest_version != sub.max_ver)""",
|
||||||
fetch=None)
|
fetch=None)
|
||||||
|
|
||||||
# Sync description from each extension's manifest.json (main branch) — manifest is source of truth for the catalog label
|
# Sync catalog display label from each extension's manifest.json (main branch).
|
||||||
|
# Source: optional `displayLabel` field. `manifest.description` is intentionally NOT used —
|
||||||
|
# it's for composer/dev consumption and is often long English; the catalog needs short Hebrew.
|
||||||
description_updates = []
|
description_updates = []
|
||||||
for name in gitea_extensions:
|
for name in gitea_extensions:
|
||||||
if _sync_description_from_manifest(name):
|
if _sync_display_label_from_manifest(name):
|
||||||
description_updates.append(name)
|
description_updates.append(name)
|
||||||
|
|
||||||
# Reload MODULES from DB
|
# Reload MODULES from DB
|
||||||
@@ -189,9 +191,12 @@ def sync_extension_registry():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _sync_description_from_manifest(module):
|
def _sync_display_label_from_manifest(module):
|
||||||
"""Fetch manifest.json from the extension's Gitea main branch and update extensions.description.
|
"""Fetch manifest.json from the extension's Gitea main branch and update extensions.description
|
||||||
Returns True if the description was changed."""
|
from manifest.displayLabel (the short Hebrew catalog label). Leaves DB untouched if displayLabel
|
||||||
|
is missing — `manifest.description` is intentionally NOT consulted (it's for composer/dev use
|
||||||
|
and is often a long English text unsuitable for the admin UI).
|
||||||
|
Returns True if the row was changed."""
|
||||||
resp = gitea_api('GET', f'/repos/{GITEA_ORG}/{module}/contents/manifest.json')
|
resp = gitea_api('GET', f'/repos/{GITEA_ORG}/{module}/contents/manifest.json')
|
||||||
if not isinstance(resp, dict) or not resp.get('content'):
|
if not isinstance(resp, dict) or not resp.get('content'):
|
||||||
return False
|
return False
|
||||||
@@ -199,14 +204,14 @@ def _sync_description_from_manifest(module):
|
|||||||
manifest = json.loads(base64.b64decode(resp['content']))
|
manifest = json.loads(base64.b64decode(resp['content']))
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
desc = (manifest.get('description') or '').strip()
|
label = (manifest.get('displayLabel') or '').strip()
|
||||||
if not desc:
|
if not label:
|
||||||
return False
|
return False
|
||||||
current = query("SELECT description FROM extensions WHERE name = %s", (module,), fetch='one')
|
current = query("SELECT description FROM extensions WHERE name = %s", (module,), fetch='one')
|
||||||
if current and (current.get('description') or '').strip() == desc:
|
if current and (current.get('description') or '').strip() == label:
|
||||||
return False
|
return False
|
||||||
query("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s",
|
query("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s",
|
||||||
(desc, module), fetch=None)
|
(label, module), fetch=None)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
SSL_CTX = ssl.create_default_context()
|
SSL_CTX = ssl.create_default_context()
|
||||||
@@ -409,10 +414,12 @@ def build_and_publish(module, version):
|
|||||||
cur.execute("""UPDATE extensions SET latest_version = %s, updated_at = NOW()
|
cur.execute("""UPDATE extensions SET latest_version = %s, updated_at = NOW()
|
||||||
WHERE name = %s AND (latest_version IS NULL OR string_to_array(latest_version, '.')::int[] < string_to_array(%s, '.')::int[])""",
|
WHERE name = %s AND (latest_version IS NULL OR string_to_array(latest_version, '.')::int[] < string_to_array(%s, '.')::int[])""",
|
||||||
(version, module, version))
|
(version, module, version))
|
||||||
manifest_desc = (manifest.get('description') or '').strip()
|
# Sync catalog display label from manifest.displayLabel (short Hebrew). Skip manifest.description
|
||||||
if manifest_desc:
|
# — it's for composer/dev use, often long English, and unsuitable for the admin UI.
|
||||||
|
manifest_label = (manifest.get('displayLabel') or '').strip()
|
||||||
|
if manifest_label:
|
||||||
cur.execute("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s",
|
cur.execute("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s",
|
||||||
(manifest_desc, module))
|
(manifest_label, module))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -792,6 +799,8 @@ class APIHandler(BaseHTTPRequestHandler):
|
|||||||
self._handle_publish(body)
|
self._handle_publish(body)
|
||||||
elif path == '/release-deleted':
|
elif path == '/release-deleted':
|
||||||
self._handle_release_deleted(body)
|
self._handle_release_deleted(body)
|
||||||
|
elif path == '/admin/set-labels':
|
||||||
|
self._handle_admin_set_labels(body)
|
||||||
elif path == '/deploy':
|
elif path == '/deploy':
|
||||||
self._handle_deploy(body)
|
self._handle_deploy(body)
|
||||||
elif path == '/deploy-all':
|
elif path == '/deploy-all':
|
||||||
@@ -940,6 +949,24 @@ class APIHandler(BaseHTTPRequestHandler):
|
|||||||
result = build_and_publish(module, version)
|
result = build_and_publish(module, version)
|
||||||
self._respond(200, result)
|
self._respond(200, result)
|
||||||
|
|
||||||
|
def _handle_admin_set_labels(self, body):
|
||||||
|
"""Bulk-set extensions.description (the catalog display label) for one or many extensions.
|
||||||
|
Body: { "labels": { "ModuleName": "תווית קצרה", ... } }"""
|
||||||
|
labels = body.get('labels', {})
|
||||||
|
if not isinstance(labels, dict) or not labels:
|
||||||
|
self._respond(400, {'error': 'labels dict required'}); return
|
||||||
|
updated, skipped = [], []
|
||||||
|
for name, label in labels.items():
|
||||||
|
if not isinstance(label, str):
|
||||||
|
skipped.append({'name': name, 'reason': 'label must be string'}); continue
|
||||||
|
ext = query("SELECT id FROM extensions WHERE name = %s", (name,), fetch='one')
|
||||||
|
if not ext:
|
||||||
|
skipped.append({'name': name, 'reason': 'not found'}); continue
|
||||||
|
query("UPDATE extensions SET description = %s, updated_at = NOW() WHERE id = %s",
|
||||||
|
(label.strip(), ext['id']), fetch=None)
|
||||||
|
updated.append(name)
|
||||||
|
self._respond(200, {'status': 'ok', 'updated': updated, 'skipped': skipped})
|
||||||
|
|
||||||
def _handle_release_deleted(self, body):
|
def _handle_release_deleted(self, body):
|
||||||
"""Triggered by Gitea release-deleted webhook (via n8n).
|
"""Triggered by Gitea release-deleted webhook (via n8n).
|
||||||
Removes the version row, recomputes latest_version, and deactivates the extension if no versions remain.
|
Removes the version row, recomputes latest_version, and deactivates the extension if no versions remain.
|
||||||
|
|||||||
Reference in New Issue
Block a user