feat(calendar): v5.8.0 calendrier & rappels + v5.7.0 database avancee (pt.2)
FlowDeck CI / lint (push) Successful in 49s
FlowDeck CI / test (push) Successful in 5m26s
FlowDeck CI / docker (push) Successful in 43s

v5.8.0 (release 5.11.7) — Calendrier & Rappels :
- moteur de recurrence RRULE subset (daily/weekly/monthly, interval,
  count, until, byweekday, timezone) — app/services/recurrence.py
- vues calendar Jour / Semaine / Mois avec expansion des occurrences
  cote serveur (GET /db/{id}/calendar/api) et popover evenement
  (Time / Timezone / Repeat / Remind)
- rappels avant echeance (scan 60 s, table reminder_log, in-app +
  email, cible = personnes assignees) — app/services/reminders.py
- fuseaux horaires : users.timezone + reglages in-app, timezone par
  evenement, liste de zones (GET /db/timezones/api)
- notifications d'assignation sur PUT /db/pages/{id}/api et
  preferences etendues (reminders, assignments)
- template Meeting notes enrichi (Agenda, Notes — migration 12)
- migrations 11-12 ; 20 tests dedies ; suite complete 523 verte

v5.7.0 (release 5.11.6, termine avant cette session, reste dans
l'arbre sans commit) — Database Avancée Pt.2 :
- proprietes person + auto-proprietes (created/last-edited time & by)
- groupes de proprietes, vues sauvegardees par utilisateur
- swimlanes, WIP limits, cartes configurables, calendar drag & drop,
  gallery couvertures ; 12 tests dedies
This commit is contained in:
2026-09-13 22:49:14 -04:00
parent 055956a351
commit d4adf89db5
18 changed files with 3301 additions and 366 deletions
+68 -2
View File
@@ -121,13 +121,15 @@ def get_user_prefs(user_id: int, conn=None) -> dict:
"SELECT notification_prefs FROM users WHERE id=?", (user_id,)
).fetchone()
if not row or not row["notification_prefs"]:
return {"comments": True, "mentions": True}
return {"comments": True, "mentions": True, "reminders": True, "assignments": True}
try:
prefs = json.loads(row["notification_prefs"])
except (TypeError, json.JSONDecodeError):
prefs = {}
return {"comments": bool(prefs.get("comments", True)),
"mentions": bool(prefs.get("mentions", True))}
"mentions": bool(prefs.get("mentions", True)),
"reminders": bool(prefs.get("reminders", True)),
"assignments": bool(prefs.get("assignments", True))}
def set_user_prefs(user_id: int, prefs: dict, conn=None) -> dict:
@@ -145,3 +147,67 @@ def set_user_prefs(user_id: int, prefs: dict, conn=None) -> dict:
)
conn.commit()
return prefs
def _person_ids(value) -> list[int]:
"""Extract user ids from a stored ``person`` property value."""
ids: list[int] = []
if isinstance(value, dict):
value = [value]
if isinstance(value, list):
for person in value:
if isinstance(person, dict) and person.get("id"):
try:
ids.append(int(person["id"]))
except (TypeError, ValueError):
pass
return ids
def notify_assignment(collection_id: int, page_id: int, page_title: str,
old_props: dict, new_props: dict, actor_id: int | None,
conn=None) -> list[int]:
"""Notify users newly assigned via a ``person`` property.
Compares old vs new property values keyed by property id; users present
in the new value but not the old one get an in-app + (opt-in) email
notification. Returns the notified user ids.
"""
def _run(c):
props = c.execute(
"SELECT id, name FROM collection_properties "
"WHERE collection_id=? AND prop_type='person'",
(collection_id,),
).fetchall()
newly: list[int] = []
for p in props:
key = str(p["id"])
before = set(_person_ids((old_props or {}).get(key)))
after = _person_ids((new_props or {}).get(key))
for uid in after:
if uid not in before and uid != actor_id and uid not in newly:
newly.append(uid)
for uid in newly:
create_notification(
uid, actor_id, "assignment",
title="Assigned to you",
message=page_title or "Untitled",
resource_type="db_page",
resource_id=page_id,
url=f"/collections/{collection_id}",
conn=c, commit=False,
)
mailer.notify_user(
uid, subject="[FlowDeck] Assigned to you",
body_text=page_title or "Untitled",
cta_url=f"/collections/{collection_id}",
prefs_key="assignments",
)
return newly
if conn is not None:
return _run(conn)
with get_conn() as c:
result = _run(c)
c.commit()
return result