feat: Organisations-Einzelabruf + Enrollment-Session-Aufladen (Phase 5)
Zwei neue Endpunkte, die die Kundenplattform-Selfservice-UI (Phase 5)
braucht:
- GET /api/v1/organizations/{id}: Einzelabruf statt der bisherigen
reinen Liste - die Liste zeigt alle Organisationen uebergreifend
(bislang nur fuer den organisationsuebergreifenden Admin-Bereich
genutzt) und waere fuer einen normalen Kunden ein Datenleck.
- PATCH /api/v1/enrollment-sessions/{id}: erhoeht Kontingent
(additiv, "aufladen" statt "neu setzen") und/oder verlaengert die
Gueltigkeit einer bestehenden Session. Eine bereits ausgeteilte,
personalisierte Kunden-ISO bettet die Session-Id fest als
Aktivierungscode ein - das Aufladen macht sie weiter nutzbar, ohne
dass eine neue ISO gebaut/neu verteilt werden muss.
Lokal end-to-end verifiziert (siehe kundenplattform-Commit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
9fc2c5f52f
commit
122e9fb499
77
app.py
77
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):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user