diff --git a/app.py b/app.py index f3413e5..6e0a2d7 100644 --- a/app.py +++ b/app.py @@ -122,6 +122,11 @@ class CreateEnrollmentSessionRequest(BaseModel): expires_at: str +class RechargeEnrollmentSessionRequest(BaseModel): + additional_devices: int = 0 + expires_at: str | None = None + + class CreateIsoBuildRequest(BaseModel): activation_code: str wifi_ssid: str | None = None @@ -773,6 +778,20 @@ def fetch_organizations(): for org_id, name, created_at in cur.fetchall() ] +def fetch_organization(organization_id: str): + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT id, name, created_at FROM organizations WHERE id = %s", + (organization_id,), + ) + row = cur.fetchone() + + if row is None: + return None + + return {"id": str(row[0]), "name": row[1], "created_at": row[2].isoformat()} + def create_organization(name: str): organization_id = uuid4() @@ -1132,6 +1151,31 @@ def revoke_enrollment_session(session_id: str): ) return cur.fetchone() is not None +def recharge_enrollment_session(session_id: str, additional_devices: int, expires_at: str | None): + """ + Phase 5 (Kundenplattform-Selfservice): eine bereits ausgeteilte, + personalisierte Kunden-ISO bettet die Enrollment-Session-Id fest als + Aktivierungscode ein (siehe build_customer_iso.sh) - Kontingent/ + Gueltigkeit dieser Session zu erhoehen macht die ISO weiter nutzbar, + ohne dass eine neue gebaut/neu verteilt werden muss. additional_devices + wird auf max_devices addiert (nicht ersetzt - "aufladen", nicht + "neu setzen"), expires_at ersetzt den bisherigen Wert, wenn angegeben. + """ + + with get_database_connection() as conn: + with conn.cursor() as cur: + cur.execute( + """ + UPDATE enrollment_sessions + SET max_devices = max_devices + %s, + expires_at = COALESCE(%s, expires_at) + WHERE id = %s AND revoked_at IS NULL + RETURNING id + """, + (additional_devices, expires_at, session_id), + ) + return cur.fetchone() is not None + def iso_build_row_to_dict(row) -> dict: (build_id, organization_id, status, output_filename, error_message, created_at, started_at, finished_at) = row @@ -1659,6 +1703,19 @@ def create_organization_endpoint( return {"success": True, "organization": create_organization(payload.name)} +@app.get("/api/v1/organizations/{organization_id}") +def get_organization_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."} + + organization = fetch_organization(organization_id) + + if organization is None: + return {"success": False, "error": "organization_not_found", "message": "Die Organisation wurde nicht gefunden."} + + return {"success": True, "organization": organization} + + @app.patch("/api/v1/organizations/{organization_id}") def update_organization_endpoint( organization_id: str, @@ -1887,6 +1944,26 @@ def revoke_enrollment_session_endpoint(session_id: str, authorization: str | Non return {"success": True} +@app.patch("/api/v1/enrollment-sessions/{session_id}") +def recharge_enrollment_session_endpoint( + session_id: str, + payload: RechargeEnrollmentSessionRequest, + 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 payload.additional_devices < 0: + return {"success": False, "error": "invalid_request", "message": "additional_devices darf nicht negativ sein."} + + recharged = recharge_enrollment_session(session_id, payload.additional_devices, payload.expires_at) + + if not recharged: + return {"success": False, "error": "not_found", "message": "Enrollment Session wurde nicht gefunden oder ist widerrufen."} + + return {"success": True} + + @app.get("/api/v1/organizations/{organization_id}/iso-builds") def list_iso_builds(organization_id: str, authorization: str | None = Header(default=None)): if not require_service_token(authorization):