feat: add personal ics feeds for assigned tasks
This commit is contained in:
@@ -17,8 +17,18 @@ def ensure_schema_and_admins() -> None:
|
||||
db.session.execute(text("ALTER TABLE user ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 0"))
|
||||
db.session.commit()
|
||||
|
||||
if "calendar_feed_token" not in column_names:
|
||||
db.session.execute(text("ALTER TABLE user ADD COLUMN calendar_feed_token VARCHAR(255)"))
|
||||
db.session.commit()
|
||||
|
||||
ensure_app_settings()
|
||||
|
||||
users_without_feed = User.query.filter(User.calendar_feed_token.is_(None)).all()
|
||||
if users_without_feed:
|
||||
for user in users_without_feed:
|
||||
user.ensure_calendar_feed_token()
|
||||
db.session.commit()
|
||||
|
||||
admin_exists = User.query.filter_by(is_admin=True).first()
|
||||
if admin_exists:
|
||||
return
|
||||
|
||||
60
app/services/calendar_feeds.py
Normal file
60
app/services/calendar_feeds.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, time, timedelta
|
||||
|
||||
from ..models import TaskInstance, User
|
||||
|
||||
|
||||
def _ics_escape(value: str | None) -> str:
|
||||
text = (value or "").replace("\\", "\\\\")
|
||||
text = text.replace(";", "\\;").replace(",", "\\,")
|
||||
text = text.replace("\r\n", "\\n").replace("\n", "\\n").replace("\r", "\\n")
|
||||
return text
|
||||
|
||||
|
||||
def _format_date(value) -> str:
|
||||
return value.strftime("%Y%m%d")
|
||||
|
||||
|
||||
def _format_timestamp(value: datetime | None) -> str:
|
||||
timestamp = value or datetime.now(UTC).replace(tzinfo=None)
|
||||
return timestamp.strftime("%Y%m%dT%H%M%SZ")
|
||||
|
||||
|
||||
def build_calendar_feed(user: User, base_url: str) -> str:
|
||||
tasks = (
|
||||
TaskInstance.query.filter_by(assigned_user_id=user.id)
|
||||
.order_by(TaskInstance.due_date.asc(), TaskInstance.id.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
lines = [
|
||||
"BEGIN:VCALENDAR",
|
||||
"VERSION:2.0",
|
||||
"PRODID:-//hnz.io//Putzliga//DE",
|
||||
"CALSCALE:GREGORIAN",
|
||||
"METHOD:PUBLISH",
|
||||
f"X-WR-CALNAME:{_ics_escape(f'Putzliga - {user.name}')}",
|
||||
f"X-WR-CALDESC:{_ics_escape('Persoenlicher Aufgabenfeed aus Putzliga')}",
|
||||
]
|
||||
|
||||
for task in tasks:
|
||||
due_end = task.due_date + timedelta(days=1)
|
||||
description = _ics_escape(task.description)
|
||||
lines.extend(
|
||||
[
|
||||
"BEGIN:VEVENT",
|
||||
f"UID:taskinstance-{task.id}@putzliga",
|
||||
f"DTSTAMP:{_format_timestamp(task.updated_at)}",
|
||||
f"LAST-MODIFIED:{_format_timestamp(task.updated_at)}",
|
||||
f"SUMMARY:{_ics_escape(task.title)}",
|
||||
f"DESCRIPTION:{description}",
|
||||
f"DTSTART;VALUE=DATE:{_format_date(task.due_date)}",
|
||||
f"DTEND;VALUE=DATE:{_format_date(due_end)}",
|
||||
f"URL:{_ics_escape(base_url)}",
|
||||
"END:VEVENT",
|
||||
]
|
||||
)
|
||||
|
||||
lines.append("END:VCALENDAR")
|
||||
return "\r\n".join(lines) + "\r\n"
|
||||
Reference in New Issue
Block a user