feat(anhaenge): ATTACH-Zeile im erzeugten ICS
Jeder Anhang wird als ATTACH mit FMTTYPE, SIZE und FILENAME ausgegeben, als URI statt inline base64 -- bei 10 MB je Datei wuerde sonst bei jeder Synchronisation der ganze Anhang im ICS-Rumpf mitgeschleppt. Die absolute URL kommt aus dav_util.public_base, das PUBLIC_BASE_URL und die Forwarded-Header schon behandelt; ohne Basis-URL wird die Zeile weggelassen, weil eine relative ATTACH-URL einem externen Client nichts nuetzt. Abschaltbar ueber ATTACHMENT_PUBLIC_LINKS=0. Der CalDAV-PUT ist geprueft: ein externer Client, der den Termin zurueckschreibt, laesst die Anhaenge unberuehrt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4b9c09df74
commit
a024fa54b9
@@ -21,6 +21,7 @@ and forgets the helper.
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
import time
|
||||
import uuid
|
||||
@@ -75,6 +76,11 @@ _TEXT_TYPES = {
|
||||
# row is committed, and a sweep landing in between would delete a good file.
|
||||
SWEEP_GRACE_SECONDS = 3600
|
||||
|
||||
# Deployments that would rather lose attachments in external calendar clients
|
||||
# than hand out unauthenticated URLs set this to 0: no ATTACH line is emitted
|
||||
# and the capability endpoint 404s.
|
||||
PUBLIC_LINKS_ENABLED = os.environ.get("ATTACHMENT_PUBLIC_LINKS", "1") != "0"
|
||||
|
||||
|
||||
def sniff(raw: bytes, filename: str) -> tuple[str, str]:
|
||||
"""Determine ``(content_type, extension)`` from the BYTES.
|
||||
@@ -243,6 +249,23 @@ def counts_for_events(db: Session, event_ids: Iterable[int]) -> dict[int, int]:
|
||||
return {event_id: n for event_id, n in rows}
|
||||
|
||||
|
||||
def by_event(db: Session, event_ids: Iterable[int]) -> dict[int, list]:
|
||||
"""Attachments grouped by event id, in ONE query — for ICS generation."""
|
||||
ids = [i for i in event_ids if i is not None]
|
||||
if not ids:
|
||||
return {}
|
||||
out: dict[int, list] = {}
|
||||
rows = (
|
||||
db.query(models.EventAttachment)
|
||||
.filter(models.EventAttachment.event_id.in_(ids))
|
||||
.order_by(models.EventAttachment.id)
|
||||
.all()
|
||||
)
|
||||
for row in rows:
|
||||
out.setdefault(row.event_id, []).append(row)
|
||||
return out
|
||||
|
||||
|
||||
def sweep(db: Session) -> tuple[int, int]:
|
||||
"""Drop attachments whose event is gone, and files with no row.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user