API: Kategorie-Art und Kategorie für Webapp-Vorlagen

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 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-07 11:28:49 +02:00
1 parent 845533a0a0
commit 8363d41dd3
1 file changed
+36 -24
+36 -24
View File
@@ -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}")