From 8363d41dd3883dd27ee6c9696d163825abe9c587 Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Wed, 7 Oct 2026 11:28:49 +0200 Subject: [PATCH] =?UTF-8?q?API:=20Kategorie-Art=20und=20Kategorie=20f?= =?UTF-8?q?=C3=BCr=20Webapp-Vorlagen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetch_kategorien() liefert art, Kategorien lassen sich als App oder Richtlinie anlegen. Webapp-Vorlagen tragen optional eine App-Kategorie, die beim Aktivieren in den Workspace mitkopiert wird. Co-Authored-By: Claude Opus 5.5 --- app.py | 60 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/app.py b/app.py index b63ed42..96f92b4 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,7 @@ import urllib.error import urllib.request from urllib.parse import urljoin, urlparse from uuid import UUID, uuid4 +from typing import Literal import psycopg from psycopg.types.json import Jsonb @@ -210,6 +211,7 @@ class CreateKategorieRequest(BaseModel): name: str description: str | None = None sort_order: int = 0 + art: Literal["app", "richtlinie"] = "app" class CreateWorkspaceRequest(BaseModel): @@ -243,12 +245,14 @@ class CreateWebappTemplateRequest(BaseModel): name: str url: str description: str | None = None + kategorie_id: str | None = None class UpdateWebappTemplateRequest(BaseModel): name: str url: str description: str | None = None + kategorie_id: str | None = None class ExtraPartitionRequest(BaseModel): @@ -2862,7 +2866,7 @@ def fetch_kategorien(): with get_database_connection() as conn: with conn.cursor() as cur: cur.execute( - "SELECT id, key, name, description, sort_order FROM kategorien ORDER BY sort_order, name" + "SELECT id, key, name, description, sort_order, art FROM kategorien ORDER BY sort_order, name" ) return [ { @@ -2871,24 +2875,25 @@ def fetch_kategorien(): "name": name, "description": description, "sort_order": sort_order, + "art": art, } - for kid, key, name, description, sort_order in cur.fetchall() + for kid, key, name, description, sort_order, art in cur.fetchall() ] -def create_kategorie(key: str, name: str, description: str | None, sort_order: int): +def create_kategorie(key: str, name: str, description: str | None, sort_order: int, art: str = "app"): kategorie_id = uuid4() with get_database_connection() as conn: with conn.cursor() as cur: cur.execute( """ - INSERT INTO kategorien (id, key, name, description, sort_order) - VALUES (%s, %s, %s, %s, %s) + INSERT INTO kategorien (id, key, name, description, sort_order, art) + VALUES (%s, %s, %s, %s, %s, %s) """, - (kategorie_id, key, name, description, sort_order), + (kategorie_id, key, name, description, sort_order, art), ) - return {"id": str(kategorie_id), "key": key, "name": name} + return {"id": str(kategorie_id), "key": key, "name": name, "art": art} def fetch_workspaces(): with get_database_connection() as conn: @@ -3242,7 +3247,12 @@ def fetch_webapp_templates(): with get_database_connection() as conn: with conn.cursor() as cur: cur.execute( - "SELECT id, name, url, description, icon_filename FROM webapp_templates ORDER BY name" + """ + SELECT t.id, t.name, t.url, t.description, t.icon_filename, t.kategorie_id, k.name + FROM webapp_templates t + LEFT JOIN kategorien k ON k.id = t.kategorie_id + ORDER BY t.name + """ ) return [ { @@ -3251,8 +3261,10 @@ def fetch_webapp_templates(): "url": url, "description": description, "icon_path": f"/webapp-icons/{icon_filename}" if icon_filename else None, + "kategorie_id": str(kategorie_id) if kategorie_id else None, + "kategorie_name": kategorie_name, } - for template_id, name, url, description, icon_filename in cur.fetchall() + for template_id, name, url, description, icon_filename, kategorie_id, kategorie_name in cur.fetchall() ] @@ -3286,7 +3298,7 @@ def fetch_available_templates_for_workspace(workspace_id: str): ] -def create_webapp_template(name: str, url: str, description: str | None): +def create_webapp_template(name: str, url: str, description: str | None, kategorie_id: str | None = None): template_id = uuid4() favicon = fetch_favicon(url) icon_filename = _write_webapp_icon(template_id, favicon[0], favicon[1]) if favicon else None @@ -3296,16 +3308,16 @@ def create_webapp_template(name: str, url: str, description: str | None): with conn.cursor() as cur: cur.execute( """ - INSERT INTO webapp_templates (id, name, url, description, icon_filename, icon_content_type) - VALUES (%s, %s, %s, %s, %s, %s) + INSERT INTO webapp_templates (id, name, url, description, icon_filename, icon_content_type, kategorie_id) + VALUES (%s, %s, %s, %s, %s, %s, %s) """, - (template_id, name, url, description, icon_filename, icon_content_type), + (template_id, name, url, description, icon_filename, icon_content_type, kategorie_id), ) return {"id": str(template_id), "name": name, "url": url, "description": description, "icon_path": f"/webapp-icons/{icon_filename}" if icon_filename else None} -def update_webapp_template(template_id: str, name: str, url: str, description: str | None): +def update_webapp_template(template_id: str, name: str, url: str, description: str | None, kategorie_id: str | None = None): favicon = fetch_favicon(url) icon_filename = _write_webapp_icon(template_id, favicon[0], favicon[1]) if favicon else None icon_content_type = favicon[1] if favicon else None @@ -3316,10 +3328,10 @@ def update_webapp_template(template_id: str, name: str, url: str, description: s """ UPDATE webapp_templates SET name = %s, url = %s, description = %s, icon_filename = %s, - icon_content_type = %s, updated_at = now() + icon_content_type = %s, kategorie_id = %s, updated_at = now() WHERE id = %s """, - (name, url, description, icon_filename, icon_content_type, template_id), + (name, url, description, icon_filename, icon_content_type, kategorie_id, template_id), ) return {"id": template_id, "name": name, "url": url, "description": description, "icon_path": f"/webapp-icons/{icon_filename}" if icon_filename else None} @@ -3350,7 +3362,7 @@ def activate_webapp_template(workspace_id: str, template_id: str): with get_database_connection() as conn: with conn.cursor() as cur: cur.execute( - "SELECT name, url, icon_filename, icon_content_type FROM webapp_templates WHERE id = %s", + "SELECT name, url, icon_filename, icon_content_type, kategorie_id FROM webapp_templates WHERE id = %s", (template_id,), ) template = cur.fetchone() @@ -3358,7 +3370,7 @@ def activate_webapp_template(workspace_id: str, template_id: str): if template is None: return None - name, url, icon_filename, icon_content_type = template + name, url, icon_filename, icon_content_type, kategorie_id = template cur.execute( "SELECT 1 FROM workspace_webapps WHERE workspace_id = %s AND template_id = %s", @@ -3386,10 +3398,10 @@ def activate_webapp_template(workspace_id: str, template_id: str): cur.execute( """ - INSERT INTO workspace_webapps (id, workspace_id, name, url, icon_filename, icon_content_type, template_id) - VALUES (%s, %s, %s, %s, %s, %s, %s) + INSERT INTO workspace_webapps (id, workspace_id, name, url, icon_filename, icon_content_type, template_id, kategorie_id) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) """, - (webapp_id, workspace_id, name, url, copied_icon_filename, icon_content_type, template_id), + (webapp_id, workspace_id, name, url, copied_icon_filename, icon_content_type, template_id, kategorie_id), ) return {"id": str(webapp_id), "name": name, "url": url, "icon_path": f"/webapp-icons/{copied_icon_filename}" if copied_icon_filename else None} @@ -4806,7 +4818,7 @@ def create_kategorie_endpoint(payload: CreateKategorieRequest, authorization: st return { "success": True, - "kategorie": create_kategorie(payload.key, payload.name, payload.description, payload.sort_order), + "kategorie": create_kategorie(payload.key, payload.name, payload.description, payload.sort_order, payload.art), } @@ -4936,7 +4948,7 @@ def create_webapp_template_endpoint(payload: CreateWebappTemplateRequest, author if not require_service_token(authorization): return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."} - return {"success": True, "webapp_template": create_webapp_template(payload.name, payload.url, payload.description)} + return {"success": True, "webapp_template": create_webapp_template(payload.name, payload.url, payload.description, payload.kategorie_id)} @app.put("/api/v1/webapp-templates/{template_id}") @@ -4944,7 +4956,7 @@ def update_webapp_template_endpoint(template_id: str, payload: UpdateWebappTempl if not require_service_token(authorization): return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."} - return {"success": True, "webapp_template": update_webapp_template(template_id, payload.name, payload.url, payload.description)} + return {"success": True, "webapp_template": update_webapp_template(template_id, payload.name, payload.url, payload.description, payload.kategorie_id)} @app.delete("/api/v1/webapp-templates/{template_id}")