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.