Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ The portal modules are referred to as applets because SPH is built on top of Moo
- `stundenplan` — timetable data
- `lerngruppen` — study groups
- `oberstufenwahl` — student-facing elections and course choices
- `dateispeicher` — school file storage and downloads
- `dateiverteilung` — targeted notices, personal files, and authenticated downloads
- `school_list` — school names and IDs for login and school selection


Expand Down
50 changes: 50 additions & 0 deletions api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ def validate_module_preferences(cls, value):
"dashboard",
"messages",
"dateispeicher",
"dateiverteilung",
"vertretungsplan",
"dsb",
"courses",
Expand Down Expand Up @@ -1995,6 +1996,55 @@ async def download_dateispeicher_file(
)


# --- Dateiverteilung ---


@app.get("/dateiverteilung")
async def get_dateiverteilung(
refresh: bool = False,
auth: AuthSession = Depends(client_dependency),
) -> Dict[str, object]:
if not refresh:
cached = await sessions.get_cached(auth.user_id, "/dateiverteilung")
if cached is not None:
return cached

result = await run_in_threadpool(auth.client.dateiverteilung_get_overview)
if result.get("success"):
await sessions.set_cache(auth.user_id, "/dateiverteilung", result)
elif result.get("error_kind") == "authentication":
await sessions.invalidate_schulportal_client(auth.user_id, auth.client)
return result


@app.get("/dateiverteilung/file")
async def download_dateiverteilung_file(
url: str = Query(..., min_length=1, max_length=2048),
auth: AuthSession = Depends(client_dependency),
):
result = await run_in_threadpool(auth.client.dateiverteilung_download_file, url)
stream = result.get("stream")
if not result.get("success") or stream is None:
error_kind = result.get("error_kind")
if error_kind == "validation":
error_status = status.HTTP_400_BAD_REQUEST
elif error_kind == "authentication":
await sessions.invalidate_schulportal_client(auth.user_id, auth.client)
error_status = status.HTTP_401_UNAUTHORIZED
elif result.get("upstream_status") == status.HTTP_404_NOT_FOUND:
error_status = status.HTTP_404_NOT_FOUND
else:
error_status = status.HTTP_502_BAD_GATEWAY
raise HTTPException(status_code=error_status, detail=result.get("error", "File not found"))

filename = re.sub(r'[\r\n"]', "_", str(result.get("filename") or "dateiverteilung-datei"))
return StreamingResponse(
content=stream,
media_type=result.get("content_type") or "application/octet-stream",
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename, safe='')}"},
)


# --- Lerngruppen ---


Expand Down
1 change: 1 addition & 0 deletions api/auth_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def _pairing_code_hash(code: str) -> str:
"dashboard",
"messages",
"dateispeicher",
"dateiverteilung",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Migrate saved sidebar orders for Dateiverteilung

Existing users who have ever saved preferences have a complete pre-upgrade sidebar.order persisted in JSON. _decode_user_preferences() merges that list by replacement, so merely adding this item to the default only affects new users; existing users keep an order without dateiverteilung until they explicitly submit another order update, even though visibility is controlled separately by hidden_items. Append newly introduced default items while decoding or migrate stored orders.

Useful? React with 👍 / 👎.

"vertretungsplan",
"dsb",
"courses",
Expand Down
1 change: 1 addition & 0 deletions api/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
("/vertretungsplan", "Plans"),
("/stundenplan", "Plans"),
("/dateispeicher", "File Storage"),
("/dateiverteilung", "File Distribution"),
("/lerngruppen", "Study Groups"),
("/school-list", "School List"),
("/benutzer", "User Info"),
Expand Down
11 changes: 11 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,17 @@ Fetch files and folders for a specific dateispeicher node.

Search files in the dateispeicher by name.

#### dateiverteilung_get_overview

Fetch targeted file distributions for the authenticated user. Returns each
distribution's title, provenance, date, unread state, files, and related links.

#### dateiverteilung_download_file

Stream a file from `dateiverteilung.php` through the authenticated portal
session. Download URLs are restricted to the configured Schulportal origin,
the Dateiverteilung endpoint, and known download actions.

#### lerngruppen_get_overview

Fetch study groups and exam data (lerngruppen.php).
Expand Down
1 change: 1 addition & 0 deletions schulportal_hessen/applets/dateiverteilung/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Dateiverteilung (targeted file distribution) applet."""
Loading