From 740866eb1ca93d5e3a7ebd1dda2e00dc0da5a597 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 25 Apr 2026 17:55:59 +0000 Subject: [PATCH] feat: separate manifest.displayLabel for catalog UI from manifest.description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- app_server.py | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/app_server.py b/app_server.py index f0a04ca..d523c53 100644 --- a/app_server.py +++ b/app_server.py @@ -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)""", 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 = [] for name in gitea_extensions: - if _sync_description_from_manifest(name): + if _sync_display_label_from_manifest(name): description_updates.append(name) # Reload MODULES from DB @@ -189,9 +191,12 @@ def sync_extension_registry(): } -def _sync_description_from_manifest(module): - """Fetch manifest.json from the extension's Gitea main branch and update extensions.description. - Returns True if the description was changed.""" +def _sync_display_label_from_manifest(module): + """Fetch manifest.json from the extension's Gitea main branch and update extensions.description + 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') if not isinstance(resp, dict) or not resp.get('content'): return False @@ -199,14 +204,14 @@ def _sync_description_from_manifest(module): manifest = json.loads(base64.b64decode(resp['content'])) except Exception: return False - desc = (manifest.get('description') or '').strip() - if not desc: + label = (manifest.get('displayLabel') or '').strip() + if not label: return False 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 query("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s", - (desc, module), fetch=None) + (label, module), fetch=None) return True 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() WHERE name = %s AND (latest_version IS NULL OR string_to_array(latest_version, '.')::int[] < string_to_array(%s, '.')::int[])""", (version, module, version)) - manifest_desc = (manifest.get('description') or '').strip() - if manifest_desc: + # Sync catalog display label from manifest.displayLabel (short Hebrew). Skip manifest.description + # — 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", - (manifest_desc, module)) + (manifest_label, module)) conn.commit() cur.close() conn.close() @@ -792,6 +799,8 @@ class APIHandler(BaseHTTPRequestHandler): self._handle_publish(body) elif path == '/release-deleted': self._handle_release_deleted(body) + elif path == '/admin/set-labels': + self._handle_admin_set_labels(body) elif path == '/deploy': self._handle_deploy(body) elif path == '/deploy-all': @@ -940,6 +949,24 @@ class APIHandler(BaseHTTPRequestHandler): result = build_and_publish(module, version) 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): """Triggered by Gitea release-deleted webhook (via n8n). Removes the version row, recomputes latest_version, and deactivates the extension if no versions remain.