feat: add /release-deleted endpoint for Gitea release deletion handler
When a release is deleted in Gitea (e.g., to retire an extension), the platform DB still showed the extension as installable. This endpoint removes the version row, recomputes latest_version, and deactivates the extension if no versions remain — making it disappear from the install catalog without touching the repo. Called by n8n workflow NwrRbHNAXFVOLHCQ on Gitea release.deleted event. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ Builds extensions from Gitea (no local files needed).
|
|||||||
|
|
||||||
Pipeline endpoints (called by n8n):
|
Pipeline endpoints (called by n8n):
|
||||||
POST /publish — Download from Gitea, build ZIP, publish to Composer Registry + Release
|
POST /publish — Download from Gitea, build ZIP, publish to Composer Registry + Release
|
||||||
|
POST /release-deleted — Remove a deleted release from DB; deactivate extension if no versions remain
|
||||||
POST /deploy — Deploy extension to a specific customer via EspoCRM API
|
POST /deploy — Deploy extension to a specific customer via EspoCRM API
|
||||||
POST /deploy-all — Deploy to all licensed customers
|
POST /deploy-all — Deploy to all licensed customers
|
||||||
POST /sync — Sync installed extensions from EspoCRM servers with platform DB
|
POST /sync — Sync installed extensions from EspoCRM servers with platform DB
|
||||||
@@ -752,6 +753,8 @@ class APIHandler(BaseHTTPRequestHandler):
|
|||||||
path = self.path.split('?')[0]
|
path = self.path.split('?')[0]
|
||||||
if path == '/publish':
|
if path == '/publish':
|
||||||
self._handle_publish(body)
|
self._handle_publish(body)
|
||||||
|
elif path == '/release-deleted':
|
||||||
|
self._handle_release_deleted(body)
|
||||||
elif path == '/deploy':
|
elif path == '/deploy':
|
||||||
self._handle_deploy(body)
|
self._handle_deploy(body)
|
||||||
elif path == '/deploy-all':
|
elif path == '/deploy-all':
|
||||||
@@ -900,6 +903,39 @@ 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_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.
|
||||||
|
Body: { module: str, version: str (optional — if omitted, only recomputes/deactivates) }"""
|
||||||
|
module = body.get('module', '')
|
||||||
|
version = body.get('version', '')
|
||||||
|
if not module:
|
||||||
|
self._respond(400, {'error': 'module required'}); return
|
||||||
|
ext = query("SELECT id FROM extensions WHERE name = %s", (module,), fetch='one')
|
||||||
|
if not ext:
|
||||||
|
self._respond(404, {'error': f'Extension {module} not found'}); return
|
||||||
|
ext_id = ext['id']
|
||||||
|
if version:
|
||||||
|
query("DELETE FROM extension_versions WHERE extension_id = %s AND version = %s",
|
||||||
|
(ext_id, version), fetch=None)
|
||||||
|
remaining = query("SELECT COUNT(*) AS cnt FROM extension_versions WHERE extension_id = %s",
|
||||||
|
(ext_id,), fetch='one')['cnt']
|
||||||
|
if remaining == 0:
|
||||||
|
query("UPDATE extensions SET is_active = false, latest_version = NULL, updated_at = NOW() WHERE id = %s",
|
||||||
|
(ext_id,), fetch=None)
|
||||||
|
self._respond(200, {'status': 'ok', 'module': module, 'version': version,
|
||||||
|
'action': 'deactivated', 'remaining_versions': 0})
|
||||||
|
return
|
||||||
|
query("""UPDATE extensions e SET latest_version = sub.max_ver, updated_at = NOW()
|
||||||
|
FROM (SELECT extension_id, version as max_ver FROM extension_versions ev1
|
||||||
|
WHERE ev1.extension_id = %s
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM extension_versions ev2
|
||||||
|
WHERE ev2.extension_id = ev1.extension_id
|
||||||
|
AND string_to_array(ev2.version, '.')::int[] > string_to_array(ev1.version, '.')::int[])
|
||||||
|
) sub WHERE e.id = sub.extension_id""", (ext_id,), fetch=None)
|
||||||
|
self._respond(200, {'status': 'ok', 'module': module, 'version': version,
|
||||||
|
'action': 'version_removed', 'remaining_versions': remaining})
|
||||||
|
|
||||||
def _handle_deploy(self, body):
|
def _handle_deploy(self, body):
|
||||||
cid, module, version = body.get('customer_id'), body.get('module',''), body.get('version','')
|
cid, module, version = body.get('customer_id'), body.get('module',''), body.get('version','')
|
||||||
if not all([cid, module, version]):
|
if not all([cid, module, version]):
|
||||||
|
|||||||
Reference in New Issue
Block a user