Workspace-Verwaltung: Entwurf/Veroeffentlichen, Wirkung, Vorlage, Kopie, Loeschen
Inhalte aendern sich nur noch im Entwurf (Endpunkte lehnen sonst mit kein_entwurf ab, Webapps muessen zum Entwurf gehoeren, neue Merkmale zur Distro passen). Veroeffentlichen prueft die Distro erneut. Neue Endpunkte: Entwurf anlegen/verwerfen, veroeffentlichen, Versionen, Nutzung (Profile, OEs, Geraete automatisch vs. gepinnt), Wirkung (Diff plus Nutzung), Name/Zweck, Vorlage mit Sichtbarkeit, Kopie-Pruefung und Kopie auf andere Distro (fehlende Merkmale fallen weg und werden gemeldet), Loeschen nur wenn ungenutzt (raeumt Icons auf), Kundengruppen. geraete_basis_herkunft() liefert die Herkunft der Grundausstattung. Workspace-Creator neu, Schritt E. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
857461cc44
commit
421e3edc45
1 file changed
+670
-4
@@ -214,6 +214,28 @@ class CreateKategorieRequest(BaseModel):
|
|||||||
art: Literal["app", "richtlinie"] = "app"
|
art: Literal["app", "richtlinie"] = "app"
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceAktualisierenRequest(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceVeroeffentlichenRequest(BaseModel):
|
||||||
|
notiz: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceVorlageRequest(BaseModel):
|
||||||
|
ist_vorlage: bool
|
||||||
|
sichtbarkeit: Literal["global", "kundengruppe", "organisation"] = "global"
|
||||||
|
kundengruppe_ids: list[str] = []
|
||||||
|
organization_ids: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceKopieRequest(BaseModel):
|
||||||
|
name: str
|
||||||
|
backend_id: str
|
||||||
|
organization_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class CreateWorkspaceAssistentRequest(BaseModel):
|
class CreateWorkspaceAssistentRequest(BaseModel):
|
||||||
"""Workspace-Creator neu, Schritt D: alles, was der Assistent sammelt."""
|
"""Workspace-Creator neu, Schritt D: alles, was der Assistent sammelt."""
|
||||||
name: str
|
name: str
|
||||||
@@ -1279,9 +1301,18 @@ def arbeitsversion_id(cur, workspace_id):
|
|||||||
|
|
||||||
|
|
||||||
def geraete_basis(cur, device_id: str):
|
def geraete_basis(cur, device_id: str):
|
||||||
|
"""Wie geraete_basis_herkunft(), ohne die Herkunft."""
|
||||||
|
|
||||||
|
ergebnis = geraete_basis_herkunft(cur, device_id)
|
||||||
|
return ergebnis[:2] if ergebnis else None
|
||||||
|
|
||||||
|
|
||||||
|
def geraete_basis_herkunft(cur, device_id: str):
|
||||||
"""
|
"""
|
||||||
Grundausstattung eines Geräts (Workspace-Creator neu, Schritt C):
|
Grundausstattung eines Geräts (Workspace-Creator neu, Schritt C):
|
||||||
(workspace_version_id, backend_id) oder None ohne Zuweisung.
|
(workspace_version_id, backend_id, herkunft) oder None ohne Zuweisung.
|
||||||
|
herkunft: {"art": "oe", "oe_id", "automatisch"} oder {"art": "installation"}
|
||||||
|
- für die Wirkungsvorschau (wer steigt bei einer neuen Version mit um).
|
||||||
|
|
||||||
Die Distro (backend_id) kommt immer aus der Installation und ändert sich
|
Die Distro (backend_id) kommt immer aus der Installation und ändert sich
|
||||||
nie. Der Workspace kommt von der nächstgelegenen OE mit Workspace (eigene
|
nie. Der Workspace kommt von der nächstgelegenen OE mit Workspace (eigene
|
||||||
@@ -1324,12 +1355,16 @@ def geraete_basis(cur, device_id: str):
|
|||||||
while aktuell is not None and aktuell in oes:
|
while aktuell is not None and aktuell in oes:
|
||||||
parent_id, workspace_id, version_id, automatisch = oes[aktuell]
|
parent_id, workspace_id, version_id, automatisch = oes[aktuell]
|
||||||
if workspace_id is not None:
|
if workspace_id is not None:
|
||||||
if automatisch or version_id is None:
|
automatisch = bool(automatisch or version_id is None)
|
||||||
|
if automatisch:
|
||||||
version_id = veroeffentlichte_version_id(cur, workspace_id)
|
version_id = veroeffentlichte_version_id(cur, workspace_id)
|
||||||
return version_id, backend_id
|
return version_id, backend_id, {"art": "oe", "oe_id": aktuell, "automatisch": automatisch}
|
||||||
aktuell = parent_id
|
aktuell = parent_id
|
||||||
|
|
||||||
return veroeffentlichte_version_id(cur, installations_workspace_id), backend_id
|
return (
|
||||||
|
veroeffentlichte_version_id(cur, installations_workspace_id), backend_id,
|
||||||
|
{"art": "installation"},
|
||||||
|
)
|
||||||
|
|
||||||
def fetch_auftragskatalog_state(device_id: str):
|
def fetch_auftragskatalog_state(device_id: str):
|
||||||
"""
|
"""
|
||||||
@@ -3109,6 +3144,445 @@ def create_workspace_mit_inhalt(
|
|||||||
return {"id": str(workspace_id), "key": key, "name": name}, None
|
return {"id": str(workspace_id), "key": key, "name": name}, None
|
||||||
|
|
||||||
|
|
||||||
|
# --- Workspace-Verwaltung (Workspace-Creator neu, Schritt E, 07.10.2026) ---
|
||||||
|
#
|
||||||
|
# Inhalte (Merkmale, Webapps) ändern sich nur noch in einem Entwurf. Die
|
||||||
|
# veröffentlichte Version ist eingefroren; Veröffentlichen macht den Entwurf
|
||||||
|
# zur neuen Version, auf die alle Geräte mit "automatisch aktualisieren"
|
||||||
|
# (bzw. ohne OE-Workspace) umsteigen. Gepinnte OEs bleiben auf ihrer Version.
|
||||||
|
|
||||||
|
def entwurf_id(cur, workspace_id):
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id FROM workspace_versionen WHERE workspace_id = %s AND status = 'entwurf'",
|
||||||
|
(workspace_id,),
|
||||||
|
)
|
||||||
|
row = cur.fetchone()
|
||||||
|
return row[0] if row else None
|
||||||
|
|
||||||
|
|
||||||
|
def _version_kopieren(cur, quelle_version_id, ziel_version_id, ziel_workspace_id, merkmal_filter=None):
|
||||||
|
"""Merkmale und Webapps einer Version in eine andere kopieren (Webapps
|
||||||
|
mit neuer ID, Icon-Datei geteilt). merkmal_filter: optionale Menge
|
||||||
|
erlaubter Merkmal-IDs."""
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"SELECT merkmal_id FROM workspace_version_merkmale WHERE workspace_version_id = %s",
|
||||||
|
(quelle_version_id,),
|
||||||
|
)
|
||||||
|
for (merkmal_id,) in cur.fetchall():
|
||||||
|
if merkmal_filter is None or merkmal_id in merkmal_filter:
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO workspace_version_merkmale (workspace_version_id, merkmal_id) VALUES (%s, %s)",
|
||||||
|
(ziel_version_id, merkmal_id),
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO workspace_webapps (id, workspace_id, workspace_version_id, name, url, icon_filename,
|
||||||
|
icon_content_type, sort_order, template_id, kategorie_id, kiosk_modus)
|
||||||
|
SELECT gen_random_uuid(), %s, %s, name, url, icon_filename, icon_content_type, sort_order,
|
||||||
|
template_id, kategorie_id, kiosk_modus
|
||||||
|
FROM workspace_webapps WHERE workspace_version_id = %s
|
||||||
|
""",
|
||||||
|
(ziel_workspace_id, ziel_version_id, quelle_version_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_entwurf_anlegen(workspace_id: str):
|
||||||
|
"""Neuer Entwurf als Kopie der neuesten veröffentlichten Version; gibt es
|
||||||
|
schon einen, wird er zurückgegeben. None, wenn der Workspace fehlt."""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT 1 FROM workspaces WHERE id = %s FOR UPDATE", (workspace_id,))
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
return None
|
||||||
|
vorhanden = entwurf_id(cur, workspace_id)
|
||||||
|
if vorhanden:
|
||||||
|
return str(vorhanden)
|
||||||
|
cur.execute("SELECT COALESCE(max(nummer), 0) + 1 FROM workspace_versionen WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
(nummer,) = cur.fetchone()
|
||||||
|
neu = uuid4()
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO workspace_versionen (id, workspace_id, nummer, status) VALUES (%s, %s, %s, 'entwurf')",
|
||||||
|
(neu, workspace_id, nummer),
|
||||||
|
)
|
||||||
|
quelle = veroeffentlichte_version_id(cur, workspace_id)
|
||||||
|
if quelle:
|
||||||
|
_version_kopieren(cur, quelle, neu, workspace_id)
|
||||||
|
return str(neu)
|
||||||
|
|
||||||
|
|
||||||
|
def _icons_aufraeumen(cur, icon_dateien):
|
||||||
|
"""Webapp-Icons löschen, die keine Zeile mehr verwendet (nach dem Löschen
|
||||||
|
von Webapp-Zeilen)."""
|
||||||
|
|
||||||
|
for datei in {d for d in icon_dateien if d}:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT 1 FROM workspace_webapps WHERE icon_filename = %s
|
||||||
|
UNION ALL SELECT 1 FROM gruppen_webapps WHERE icon_filename = %s
|
||||||
|
UNION ALL SELECT 1 FROM webapp_templates WHERE icon_filename = %s
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(datei, datei, datei),
|
||||||
|
)
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
pfad = WEBAPP_ICONS_DIR / datei
|
||||||
|
if pfad.is_file():
|
||||||
|
pfad.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_entwurf_verwerfen(workspace_id: str) -> bool:
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
vid = entwurf_id(cur, workspace_id)
|
||||||
|
if vid is None:
|
||||||
|
return False
|
||||||
|
cur.execute("SELECT icon_filename FROM workspace_webapps WHERE workspace_version_id = %s", (vid,))
|
||||||
|
icons = [r[0] for r in cur.fetchall()]
|
||||||
|
cur.execute("DELETE FROM workspace_versionen WHERE id = %s", (vid,))
|
||||||
|
_icons_aufraeumen(cur, icons)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_veroeffentlichen(workspace_id: str, notiz: str | None):
|
||||||
|
"""Rückgabe: (nummer, None) oder (None, (fehlercode, nachricht))."""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
vid = entwurf_id(cur, workspace_id)
|
||||||
|
if vid is None:
|
||||||
|
return None, ("kein_entwurf", "Es gibt keinen Entwurf zum Veröffentlichen.")
|
||||||
|
# Gleiche Distro-Regel wie beim Anlegen: nichts veröffentlichen,
|
||||||
|
# was auf der Distro des Workspace still fehlen würde.
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT m.name FROM workspace_version_merkmale vm
|
||||||
|
JOIN merkmale m ON m.id = vm.merkmal_id
|
||||||
|
JOIN workspaces w ON w.id = %s
|
||||||
|
LEFT JOIN blueprints bp ON bp.merkmal_id = m.id AND bp.backend_id = w.backend_id
|
||||||
|
WHERE vm.workspace_version_id = %s AND w.backend_id IS NOT NULL AND bp.id IS NULL
|
||||||
|
ORDER BY m.name
|
||||||
|
""",
|
||||||
|
(workspace_id, vid),
|
||||||
|
)
|
||||||
|
fehlend = [r[0] for r in cur.fetchall()]
|
||||||
|
if fehlend:
|
||||||
|
return None, ("distro_konflikt", "Auf der Distro des Workspace nicht umgesetzt: " + ", ".join(fehlend))
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
UPDATE workspace_versionen SET status = 'veroeffentlicht', veroeffentlicht_am = now(), notiz = %s
|
||||||
|
WHERE id = %s RETURNING nummer
|
||||||
|
""",
|
||||||
|
(notiz or None, vid),
|
||||||
|
)
|
||||||
|
(nummer,) = cur.fetchone()
|
||||||
|
cur.execute("UPDATE workspaces SET updated_at = now() WHERE id = %s", (workspace_id,))
|
||||||
|
return nummer, None
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_versionen(workspace_id: str) -> list[dict]:
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT v.id, v.nummer, v.status, v.notiz, v.erstellt_am, v.veroeffentlicht_am,
|
||||||
|
(SELECT count(*) FROM workspace_version_merkmale vm WHERE vm.workspace_version_id = v.id),
|
||||||
|
(SELECT count(*) FROM workspace_webapps ww WHERE ww.workspace_version_id = v.id)
|
||||||
|
FROM workspace_versionen v WHERE v.workspace_id = %s ORDER BY v.nummer DESC
|
||||||
|
""",
|
||||||
|
(workspace_id,),
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": str(vid), "nummer": nummer, "status": status, "notiz": notiz,
|
||||||
|
"erstellt_am": erstellt.isoformat() if erstellt else None,
|
||||||
|
"veroeffentlicht_am": veroeff.isoformat() if veroeff else None,
|
||||||
|
"anzahl_merkmale": anz_m, "anzahl_webapps": anz_w,
|
||||||
|
}
|
||||||
|
for vid, nummer, status, notiz, erstellt, veroeff, anz_m, anz_w in cur.fetchall()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_nutzung(workspace_id: str) -> dict:
|
||||||
|
"""
|
||||||
|
Wer nutzt diesen Workspace: Installationsprofile (künftige
|
||||||
|
Installationen), OEs (mit Version/Automatik) und Geräte (aktive, nicht
|
||||||
|
archivierte) - letztere aufgeteilt in "steigen bei neuer Version
|
||||||
|
automatisch um" und "auf Version N gepinnt".
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT bv.id, bv.label, o.name FROM bereitstellungsvorlagen bv
|
||||||
|
JOIN organizations o ON o.id = bv.organization_id
|
||||||
|
WHERE bv.workspace_id = %s ORDER BY o.name, bv.label
|
||||||
|
""",
|
||||||
|
(workspace_id,),
|
||||||
|
)
|
||||||
|
profile = [{"id": str(i), "label": l, "organisation": o} for i, l, o in cur.fetchall()]
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT oe.id, oe.name, o.name, oe.automatisch_aktualisieren, v.nummer
|
||||||
|
FROM organisationseinheiten oe
|
||||||
|
JOIN organizations o ON o.id = oe.organization_id
|
||||||
|
LEFT JOIN workspace_versionen v ON v.id = oe.workspace_version_id
|
||||||
|
WHERE oe.workspace_id = %s ORDER BY o.name, oe.name
|
||||||
|
""",
|
||||||
|
(workspace_id,),
|
||||||
|
)
|
||||||
|
oes = [
|
||||||
|
{"id": str(i), "name": n, "organisation": o, "automatisch": a or nr is None, "version": nr}
|
||||||
|
for i, n, o, a, nr in cur.fetchall()
|
||||||
|
]
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT d.id, o.name FROM devices d
|
||||||
|
JOIN organizations o ON o.id = d.organization_id
|
||||||
|
JOIN assignments a ON a.device_id = d.id
|
||||||
|
WHERE d.archived_at IS NULL AND d.deprovisioned_at IS NULL
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
geraete = cur.fetchall()
|
||||||
|
cur.execute("SELECT id, nummer FROM workspace_versionen WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
eigene_versionen = {vid: nr for vid, nr in cur.fetchall()}
|
||||||
|
|
||||||
|
automatisch, gepinnt, organisationen = 0, {}, set()
|
||||||
|
for device_id, org_name in geraete:
|
||||||
|
basis = geraete_basis_herkunft(cur, str(device_id))
|
||||||
|
if basis is None or basis[0] not in eigene_versionen:
|
||||||
|
continue
|
||||||
|
organisationen.add(org_name)
|
||||||
|
if basis[2]["art"] == "installation" or basis[2]["automatisch"]:
|
||||||
|
automatisch += 1
|
||||||
|
else:
|
||||||
|
nr = eigene_versionen[basis[0]]
|
||||||
|
gepinnt[nr] = gepinnt.get(nr, 0) + 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"profile": profile,
|
||||||
|
"oes": oes,
|
||||||
|
"geraete_automatisch": automatisch,
|
||||||
|
"geraete_gepinnt": [{"version": nr, "anzahl": n} for nr, n in sorted(gepinnt.items())],
|
||||||
|
"organisationen": sorted(organisationen | {p["organisation"] for p in profile} | {o["organisation"] for o in oes}),
|
||||||
|
"in_benutzung": bool(profile or oes or automatisch or gepinnt),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_wirkung(workspace_id: str) -> dict | None:
|
||||||
|
"""Wirkungsvorschau für das Veröffentlichen des Entwurfs: Unterschiede
|
||||||
|
zur neuesten veröffentlichten Version plus Nutzung."""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
neu = entwurf_id(cur, workspace_id)
|
||||||
|
if neu is None:
|
||||||
|
return None
|
||||||
|
alt = veroeffentlichte_version_id(cur, workspace_id)
|
||||||
|
|
||||||
|
def merkmale(vid):
|
||||||
|
if vid is None:
|
||||||
|
return {}
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT m.id, m.name, k.art FROM workspace_version_merkmale vm
|
||||||
|
JOIN merkmale m ON m.id = vm.merkmal_id
|
||||||
|
LEFT JOIN kategorien k ON k.id = m.kategorie_id
|
||||||
|
WHERE vm.workspace_version_id = %s
|
||||||
|
""",
|
||||||
|
(vid,),
|
||||||
|
)
|
||||||
|
return {mid: (name, art or "app") for mid, name, art in cur.fetchall()}
|
||||||
|
|
||||||
|
def webapps(vid):
|
||||||
|
if vid is None:
|
||||||
|
return set()
|
||||||
|
cur.execute("SELECT name, url FROM workspace_webapps WHERE workspace_version_id = %s", (vid,))
|
||||||
|
return set(cur.fetchall())
|
||||||
|
|
||||||
|
m_alt, m_neu = merkmale(alt), merkmale(neu)
|
||||||
|
w_alt, w_neu = webapps(alt), webapps(neu)
|
||||||
|
|
||||||
|
def namen(ids, quelle, art):
|
||||||
|
return sorted(quelle[i][0] for i in ids if quelle[i][1] == art)
|
||||||
|
|
||||||
|
hinzu, weg = set(m_neu) - set(m_alt), set(m_alt) - set(m_neu)
|
||||||
|
return {
|
||||||
|
"apps_neu": namen(hinzu, m_neu, "app"),
|
||||||
|
"apps_weg": namen(weg, m_alt, "app"),
|
||||||
|
"richtlinien_neu": namen(hinzu, m_neu, "richtlinie"),
|
||||||
|
"richtlinien_weg": namen(weg, m_alt, "richtlinie"),
|
||||||
|
"webapps_neu": sorted(n for n, _u in w_neu - w_alt),
|
||||||
|
"webapps_weg": sorted(n for n, _u in w_alt - w_neu),
|
||||||
|
"unveraendert": not (hinzu or weg or w_neu != w_alt),
|
||||||
|
"nutzung": workspace_nutzung(workspace_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_aktualisieren(workspace_id: str, name: str, description: str | None) -> bool:
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"UPDATE workspaces SET name = %s, description = %s, updated_at = now() WHERE id = %s RETURNING id",
|
||||||
|
(name.strip(), description or None, workspace_id),
|
||||||
|
)
|
||||||
|
return cur.fetchone() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_vorlage_setzen(
|
||||||
|
workspace_id: str, ist_vorlage: bool, sichtbarkeit: str,
|
||||||
|
kundengruppe_ids: list[str], organization_ids: list[str],
|
||||||
|
):
|
||||||
|
"""Vorlagen-Kennzeichen und für wen sie sichtbar ist. Rückgabe wie
|
||||||
|
workspace_veroeffentlichen()."""
|
||||||
|
|
||||||
|
if sichtbarkeit not in ("global", "kundengruppe", "organisation"):
|
||||||
|
return None, ("invalid_request", "Unbekannte Sichtbarkeit.")
|
||||||
|
if ist_vorlage and sichtbarkeit == "kundengruppe" and not kundengruppe_ids:
|
||||||
|
return None, ("invalid_request", "Bitte mindestens eine Kundengruppe wählen.")
|
||||||
|
if ist_vorlage and sichtbarkeit == "organisation" and not organization_ids:
|
||||||
|
return None, ("invalid_request", "Bitte mindestens eine Organisation wählen.")
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"UPDATE workspaces SET ist_vorlage = %s, sichtbarkeit = %s, updated_at = now() WHERE id = %s RETURNING id",
|
||||||
|
(ist_vorlage, sichtbarkeit, workspace_id),
|
||||||
|
)
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
return None, ("not_found", "Workspace wurde nicht gefunden.")
|
||||||
|
cur.execute("DELETE FROM workspace_freigabe_kundengruppen WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
cur.execute("DELETE FROM workspace_freigabe_organisationen WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
if sichtbarkeit == "kundengruppe":
|
||||||
|
for kg in dict.fromkeys(kundengruppe_ids):
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO workspace_freigabe_kundengruppen (workspace_id, kundengruppe_id) VALUES (%s, %s)",
|
||||||
|
(workspace_id, kg),
|
||||||
|
)
|
||||||
|
if sichtbarkeit == "organisation":
|
||||||
|
for org in dict.fromkeys(organization_ids):
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO workspace_freigabe_organisationen (workspace_id, organization_id) VALUES (%s, %s)",
|
||||||
|
(workspace_id, org),
|
||||||
|
)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_freigaben(workspace_id: str) -> dict:
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT kundengruppe_id FROM workspace_freigabe_kundengruppen WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
kgs = [str(r[0]) for r in cur.fetchall()]
|
||||||
|
cur.execute("SELECT organization_id FROM workspace_freigabe_organisationen WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
orgs = [str(r[0]) for r in cur.fetchall()]
|
||||||
|
return {"kundengruppe_ids": kgs, "organization_ids": orgs}
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_kundengruppen() -> list[dict]:
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT id, key, name FROM kundengruppen ORDER BY sort_order, name")
|
||||||
|
return [{"id": str(i), "key": k, "name": n} for i, k, n in cur.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_kopie_pruefen(workspace_id: str, backend_id: str) -> list[str] | None:
|
||||||
|
"""Merkmale der neuesten veröffentlichten Version, die auf der Ziel-Distro
|
||||||
|
nicht umgesetzt sind und bei einer Kopie wegfallen würden."""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
vid = veroeffentlichte_version_id(cur, workspace_id)
|
||||||
|
if vid is None:
|
||||||
|
return None
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT m.name FROM workspace_version_merkmale vm
|
||||||
|
JOIN merkmale m ON m.id = vm.merkmal_id
|
||||||
|
LEFT JOIN blueprints bp ON bp.merkmal_id = m.id AND bp.backend_id = %s
|
||||||
|
WHERE vm.workspace_version_id = %s AND bp.id IS NULL ORDER BY m.name
|
||||||
|
""",
|
||||||
|
(backend_id, vid),
|
||||||
|
)
|
||||||
|
return [r[0] for r in cur.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_kopieren(workspace_id: str, name: str, backend_id: str, organization_id: str | None):
|
||||||
|
"""Kopie der neuesten veröffentlichten Version als neuer Workspace
|
||||||
|
(Version 1) - auch auf eine andere Distro; Merkmale, die dort fehlen,
|
||||||
|
fallen weg und werden zurückgemeldet."""
|
||||||
|
|
||||||
|
name = name.strip()
|
||||||
|
if not name:
|
||||||
|
return None, ("invalid_request", "Bitte einen Namen angeben.")
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
quelle = veroeffentlichte_version_id(cur, workspace_id)
|
||||||
|
if quelle is None:
|
||||||
|
return None, ("not_found", "Workspace oder veröffentlichte Version nicht gefunden.")
|
||||||
|
cur.execute("SELECT description FROM workspaces WHERE id = %s", (workspace_id,))
|
||||||
|
(description,) = cur.fetchone()
|
||||||
|
cur.execute("SELECT 1 FROM backends WHERE id = %s", (backend_id,))
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
return None, ("backend_not_found", "Die gewählte Distro gibt es nicht.")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT vm.merkmal_id, m.name, bp.id IS NOT NULL FROM workspace_version_merkmale vm
|
||||||
|
JOIN merkmale m ON m.id = vm.merkmal_id
|
||||||
|
LEFT JOIN blueprints bp ON bp.merkmal_id = vm.merkmal_id AND bp.backend_id = %s
|
||||||
|
WHERE vm.workspace_version_id = %s
|
||||||
|
""",
|
||||||
|
(backend_id, quelle),
|
||||||
|
)
|
||||||
|
zeilen = cur.fetchall()
|
||||||
|
erlaubt = {mid for mid, _n, ok in zeilen if ok}
|
||||||
|
weggefallen = sorted(n for _mid, n, ok in zeilen if not ok)
|
||||||
|
|
||||||
|
neu_ws, neu_v = uuid4(), uuid4()
|
||||||
|
key = workspace_key_aus_name(cur, name, organization_id)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO workspaces (id, organization_id, key, name, description, backend_id, sichtbarkeit, ist_vorlage)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, FALSE)
|
||||||
|
""",
|
||||||
|
(neu_ws, organization_id, key, name, description, backend_id,
|
||||||
|
"global" if organization_id is None else "organisation"),
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO workspace_versionen (id, workspace_id, nummer, status, notiz, veroeffentlicht_am)
|
||||||
|
VALUES (%s, %s, 1, 'veroeffentlicht', %s, now())
|
||||||
|
""",
|
||||||
|
(neu_v, neu_ws, "Kopie von Workspace " + str(workspace_id)),
|
||||||
|
)
|
||||||
|
_version_kopieren(cur, quelle, neu_v, neu_ws, merkmal_filter=erlaubt)
|
||||||
|
|
||||||
|
return {"id": str(neu_ws), "key": key, "name": name, "weggefallen": weggefallen}, None
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_loeschen(workspace_id: str):
|
||||||
|
"""Löscht einen Workspace samt Versionen - nur wenn ihn nichts mehr nutzt
|
||||||
|
(kein Profil, keine OE, kein Gerät)."""
|
||||||
|
|
||||||
|
nutzung = workspace_nutzung(workspace_id)
|
||||||
|
if nutzung["in_benutzung"]:
|
||||||
|
return None, ("in_benutzung", "Der Workspace wird noch verwendet und kann nicht gelöscht werden.")
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT icon_filename FROM workspace_webapps WHERE workspace_id = %s", (workspace_id,))
|
||||||
|
icons = [r[0] for r in cur.fetchall()]
|
||||||
|
cur.execute("DELETE FROM workspaces WHERE id = %s RETURNING id", (workspace_id,))
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
return None, ("not_found", "Workspace wurde nicht gefunden.")
|
||||||
|
_icons_aufraeumen(cur, icons)
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def clone_workspace(source_workspace_id: str, organization_id: str):
|
def clone_workspace(source_workspace_id: str, organization_id: str):
|
||||||
"""
|
"""
|
||||||
"Diesen Standard übernehmen" (GUI-Umbau Phase 3, Nachtrag 11.09.2026):
|
"Diesen Standard übernehmen" (GUI-Umbau Phase 3, Nachtrag 11.09.2026):
|
||||||
@@ -5048,6 +5522,139 @@ def clone_workspace_endpoint(
|
|||||||
return {"success": True, "workspace": neuer_workspace}
|
return {"success": True, "workspace": neuer_workspace}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/kundengruppen")
|
||||||
|
def list_kundengruppen(authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
return {"success": True, "kundengruppen": fetch_kundengruppen()}
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/api/v1/workspaces/{workspace_id}")
|
||||||
|
def update_workspace_endpoint(
|
||||||
|
workspace_id: str, payload: WorkspaceAktualisierenRequest, authorization: str | None = Header(default=None)
|
||||||
|
):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if not payload.name.strip():
|
||||||
|
return {"success": False, "error": "invalid_request", "message": "Bitte einen Namen angeben."}
|
||||||
|
if not workspace_aktualisieren(workspace_id, payload.name, payload.description):
|
||||||
|
return {"success": False, "error": "not_found", "message": "Workspace wurde nicht gefunden."}
|
||||||
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/api/v1/workspaces/{workspace_id}")
|
||||||
|
def delete_workspace_endpoint(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
_ok, fehler = workspace_loeschen(workspace_id)
|
||||||
|
if fehler:
|
||||||
|
return {"success": False, "error": fehler[0], "message": fehler[1]}
|
||||||
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/workspaces/{workspace_id}/versionen")
|
||||||
|
def list_workspace_versionen(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
return {"success": True, "versionen": workspace_versionen(workspace_id)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/workspaces/{workspace_id}/entwurf")
|
||||||
|
def create_workspace_entwurf(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
vid = workspace_entwurf_anlegen(workspace_id)
|
||||||
|
if vid is None:
|
||||||
|
return {"success": False, "error": "not_found", "message": "Workspace wurde nicht gefunden."}
|
||||||
|
return {"success": True, "version_id": vid}
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/api/v1/workspaces/{workspace_id}/entwurf")
|
||||||
|
def delete_workspace_entwurf(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if not workspace_entwurf_verwerfen(workspace_id):
|
||||||
|
return {"success": False, "error": "kein_entwurf", "message": "Es gibt keinen Entwurf."}
|
||||||
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/workspaces/{workspace_id}/veroeffentlichen")
|
||||||
|
def publish_workspace_entwurf(
|
||||||
|
workspace_id: str, payload: WorkspaceVeroeffentlichenRequest, authorization: str | None = Header(default=None)
|
||||||
|
):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
nummer, fehler = workspace_veroeffentlichen(workspace_id, payload.notiz)
|
||||||
|
if fehler:
|
||||||
|
return {"success": False, "error": fehler[0], "message": fehler[1]}
|
||||||
|
return {"success": True, "nummer": nummer}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/workspaces/{workspace_id}/nutzung")
|
||||||
|
def get_workspace_nutzung(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
return {"success": True, "nutzung": workspace_nutzung(workspace_id)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/workspaces/{workspace_id}/wirkung")
|
||||||
|
def get_workspace_wirkung(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
wirkung = workspace_wirkung(workspace_id)
|
||||||
|
if wirkung is None:
|
||||||
|
return {"success": False, "error": "kein_entwurf", "message": "Es gibt keinen Entwurf."}
|
||||||
|
return {"success": True, "wirkung": wirkung}
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/api/v1/workspaces/{workspace_id}/vorlage")
|
||||||
|
def set_workspace_vorlage(
|
||||||
|
workspace_id: str, payload: WorkspaceVorlageRequest, authorization: str | None = Header(default=None)
|
||||||
|
):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
_ok, fehler = workspace_vorlage_setzen(
|
||||||
|
workspace_id, payload.ist_vorlage, payload.sichtbarkeit, payload.kundengruppe_ids, payload.organization_ids
|
||||||
|
)
|
||||||
|
if fehler:
|
||||||
|
return {"success": False, "error": fehler[0], "message": fehler[1]}
|
||||||
|
return {"success": True}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/workspaces/{workspace_id}/kopie-pruefung")
|
||||||
|
def check_workspace_kopie(workspace_id: str, backend_id: str, authorization: str | None = Header(default=None)):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
fehlend = workspace_kopie_pruefen(workspace_id, backend_id)
|
||||||
|
if fehlend is None:
|
||||||
|
return {"success": False, "error": "not_found", "message": "Workspace wurde nicht gefunden."}
|
||||||
|
return {"success": True, "weggefallen": fehlend}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/workspaces/{workspace_id}/kopie")
|
||||||
|
def copy_workspace_endpoint(
|
||||||
|
workspace_id: str, payload: WorkspaceKopieRequest, authorization: str | None = Header(default=None)
|
||||||
|
):
|
||||||
|
if not require_service_token(authorization):
|
||||||
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
kopie, fehler = workspace_kopieren(workspace_id, payload.name, payload.backend_id, payload.organization_id)
|
||||||
|
if fehler:
|
||||||
|
return {"success": False, "error": fehler[0], "message": fehler[1]}
|
||||||
|
return {"success": True, "workspace": kopie}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/workspaces/{workspace_id}")
|
@app.get("/api/v1/workspaces/{workspace_id}")
|
||||||
def get_workspace(workspace_id: str, authorization: str | None = Header(default=None)):
|
def get_workspace(workspace_id: str, authorization: str | None = Header(default=None)):
|
||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
@@ -5058,14 +5665,58 @@ def get_workspace(workspace_id: str, authorization: str | None = Header(default=
|
|||||||
if workspace is None:
|
if workspace is None:
|
||||||
return {"success": False, "error": "not_found", "message": "Workspace wurde nicht gefunden."}
|
return {"success": False, "error": "not_found", "message": "Workspace wurde nicht gefunden."}
|
||||||
|
|
||||||
|
workspace.update(workspace_freigaben(workspace_id))
|
||||||
return {"success": True, "workspace": workspace}
|
return {"success": True, "workspace": workspace}
|
||||||
|
|
||||||
|
|
||||||
|
def _nur_im_entwurf(workspace_id: str, webapp_id: str | None = None, merkmal_id: str | None = None):
|
||||||
|
"""
|
||||||
|
Inhaltliche Änderungen gehen seit Schritt E (07.10.2026) nur in einen
|
||||||
|
Entwurf - die veröffentlichte Version ist eingefroren. Prüft zusätzlich,
|
||||||
|
dass eine Webapp zu diesem Entwurf gehört und ein neues Merkmal auf der
|
||||||
|
Distro des Workspace umgesetzt ist. Gibt eine Fehlerantwort oder None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
vid = entwurf_id(cur, workspace_id)
|
||||||
|
if vid is None:
|
||||||
|
return {"success": False, "error": "kein_entwurf",
|
||||||
|
"message": "Änderungen gehen nur in einem Entwurf - bitte zuerst einen Entwurf anlegen."}
|
||||||
|
if webapp_id is not None:
|
||||||
|
cur.execute(
|
||||||
|
"SELECT 1 FROM workspace_webapps WHERE id = %s AND workspace_version_id = %s",
|
||||||
|
(webapp_id, vid),
|
||||||
|
)
|
||||||
|
if cur.fetchone() is None:
|
||||||
|
return {"success": False, "error": "not_found", "message": "Webapp gehört nicht zum Entwurf."}
|
||||||
|
if merkmal_id is not None:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT m.name, w.backend_id IS NULL OR bp.id IS NOT NULL
|
||||||
|
FROM merkmale m JOIN workspaces w ON w.id = %s
|
||||||
|
LEFT JOIN blueprints bp ON bp.merkmal_id = m.id AND bp.backend_id = w.backend_id
|
||||||
|
WHERE m.id = %s
|
||||||
|
""",
|
||||||
|
(workspace_id, merkmal_id),
|
||||||
|
)
|
||||||
|
row = cur.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return {"success": False, "error": "not_found", "message": "Merkmal wurde nicht gefunden."}
|
||||||
|
if not row[1]:
|
||||||
|
return {"success": False, "error": "distro_konflikt",
|
||||||
|
"message": f"{row[0]} ist auf der Distro dieses Workspace nicht umgesetzt."}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/v1/workspaces/{workspace_id}/merkmale/{merkmal_id}")
|
@app.post("/api/v1/workspaces/{workspace_id}/merkmale/{merkmal_id}")
|
||||||
def add_workspace_merkmal(workspace_id: str, merkmal_id: str, authorization: str | None = Header(default=None)):
|
def add_workspace_merkmal(workspace_id: str, merkmal_id: str, authorization: str | None = Header(default=None)):
|
||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if fehler := _nur_im_entwurf(workspace_id, merkmal_id=merkmal_id):
|
||||||
|
return fehler
|
||||||
|
|
||||||
add_merkmal_to_workspace(workspace_id, merkmal_id)
|
add_merkmal_to_workspace(workspace_id, merkmal_id)
|
||||||
|
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
@@ -5076,6 +5727,9 @@ def remove_workspace_merkmal(workspace_id: str, merkmal_id: str, authorization:
|
|||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if fehler := _nur_im_entwurf(workspace_id):
|
||||||
|
return fehler
|
||||||
|
|
||||||
remove_merkmal_from_workspace(workspace_id, merkmal_id)
|
remove_merkmal_from_workspace(workspace_id, merkmal_id)
|
||||||
|
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
@@ -5094,6 +5748,9 @@ def create_workspace_webapp(workspace_id: str, payload: CreateWebappRequest, aut
|
|||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if fehler := _nur_im_entwurf(workspace_id):
|
||||||
|
return fehler
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"webapp": create_webapp(
|
"webapp": create_webapp(
|
||||||
@@ -5108,6 +5765,9 @@ def update_workspace_webapp(workspace_id: str, webapp_id: str, payload: UpdateWe
|
|||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if fehler := _nur_im_entwurf(workspace_id, webapp_id=webapp_id):
|
||||||
|
return fehler
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"webapp": update_webapp(
|
"webapp": update_webapp(
|
||||||
@@ -5122,6 +5782,9 @@ def delete_workspace_webapp(workspace_id: str, webapp_id: str, authorization: st
|
|||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if fehler := _nur_im_entwurf(workspace_id, webapp_id=webapp_id):
|
||||||
|
return fehler
|
||||||
|
|
||||||
delete_webapp(webapp_id)
|
delete_webapp(webapp_id)
|
||||||
|
|
||||||
return {"success": True}
|
return {"success": True}
|
||||||
@@ -5174,6 +5837,9 @@ def activate_webapp_template_endpoint(workspace_id: str, template_id: str, autho
|
|||||||
if not require_service_token(authorization):
|
if not require_service_token(authorization):
|
||||||
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
|
||||||
|
|
||||||
|
if fehler := _nur_im_entwurf(workspace_id):
|
||||||
|
return fehler
|
||||||
|
|
||||||
webapp = activate_webapp_template(workspace_id, template_id)
|
webapp = activate_webapp_template(workspace_id, template_id)
|
||||||
|
|
||||||
if webapp is None:
|
if webapp is None:
|
||||||
|
|||||||
Reference in new issue
Block a user