feat: Organisationen + Aktivierungscodes verwalten (Kundenplattform-Admin)
Neue Endpoints GET/POST /api/v1/organizations und GET/POST
/api/v1/organizations/{id}/activation-codes für den neuen Kunden-
Management-Bereich der Kundenplattform. Aktivierungscodes werden
serverseitig generiert (kein manuelles Ausdenken nötig).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
1e9020c5ba
commit
e043233aea
115
app.py
115
app.py
@ -61,6 +61,10 @@ class AuftragSelectRequest(BaseModel):
|
|||||||
optionen: dict = {}
|
optionen: dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateOrganizationRequest(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
def write_log(entry: dict) -> None:
|
def write_log(entry: dict) -> None:
|
||||||
with LOG_PATH.open("a", encoding="utf-8") as f:
|
with LOG_PATH.open("a", encoding="utf-8") as f:
|
||||||
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
||||||
@ -599,6 +603,60 @@ def fetch_devices_for_organization(organization_id: str):
|
|||||||
for device_id, hostname, fingerprint, last_checkin in cur.fetchall()
|
for device_id, hostname, fingerprint, last_checkin in cur.fetchall()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def fetch_organizations():
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id, name, created_at FROM organizations ORDER BY name"
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{"id": str(org_id), "name": name, "created_at": created_at.isoformat()}
|
||||||
|
for org_id, name, created_at in cur.fetchall()
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_organization(name: str):
|
||||||
|
organization_id = uuid4()
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO organizations (id, name) VALUES (%s, %s)",
|
||||||
|
(organization_id, name),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"id": str(organization_id), "name": name}
|
||||||
|
|
||||||
|
def fetch_activation_codes(organization_id: str):
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT code, active, created_at
|
||||||
|
FROM activation_codes
|
||||||
|
WHERE organization_id = %s
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
""",
|
||||||
|
(organization_id,),
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{"code": code, "active": active, "created_at": created_at.isoformat()}
|
||||||
|
for code, active, created_at in cur.fetchall()
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_activation_code(organization_id: str):
|
||||||
|
code = "-".join(
|
||||||
|
secrets.token_hex(3).upper()[i:i + 3] for i in range(0, 6, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO activation_codes (code, organization_id) VALUES (%s, %s)",
|
||||||
|
(code, organization_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"code": code, "active": True}
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {
|
return {
|
||||||
@ -904,6 +962,63 @@ def get_organization_devices(
|
|||||||
return {"success": True, "devices": fetch_devices_for_organization(organization_id)}
|
return {"success": True, "devices": fetch_devices_for_organization(organization_id)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/organizations")
|
||||||
|
def list_organizations(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, "organizations": fetch_organizations()}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/organizations")
|
||||||
|
def create_organization_endpoint(
|
||||||
|
payload: CreateOrganizationRequest,
|
||||||
|
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, "organization": create_organization(payload.name)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/organizations/{organization_id}/activation-codes")
|
||||||
|
def list_activation_codes(
|
||||||
|
organization_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, "activation_codes": fetch_activation_codes(organization_id)}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/organizations/{organization_id}/activation-codes")
|
||||||
|
def create_activation_code_endpoint(
|
||||||
|
organization_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, "activation_code": create_activation_code(organization_id)}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/devices/{device_id}/auftragskatalog")
|
@app.get("/api/v1/devices/{device_id}/auftragskatalog")
|
||||||
def get_device_auftragskatalog(
|
def get_device_auftragskatalog(
|
||||||
device_id: str,
|
device_id: str,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user