feat(calendar): v5.8.0 calendrier & rappels + v5.7.0 database avancee (pt.2)
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:
@@ -419,3 +419,93 @@ def _migration_import_framework(conn: sqlite3.Connection) -> None:
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_import_jobs_created ON import_jobs(created_at)"
|
||||
)
|
||||
|
||||
|
||||
@register(10, "v5.7.0: property groups, per-user views, row covers")
|
||||
def _migration_v57_db_advanced(conn: sqlite3.Connection) -> None:
|
||||
"""v5.7.0 — Database Avancée (Pt. 2).
|
||||
|
||||
``collection_properties.group_name`` — groups properties into collapsible
|
||||
sections in the table header (Notion property groups).
|
||||
``collection_views.created_by`` — owner of a saved view; ``NULL`` means
|
||||
a shared/legacy view visible to everyone, otherwise it is personal to a user.
|
||||
``collection_pages.cover_url`` — per-row cover image (gallery/board
|
||||
cards), independent from the block-page ``pages.cover_url``.
|
||||
"""
|
||||
_pcols = {r[1] for r in conn.execute("PRAGMA table_info(collection_properties)").fetchall()}
|
||||
if "group_name" not in _pcols:
|
||||
conn.execute(
|
||||
"ALTER TABLE collection_properties ADD COLUMN group_name TEXT NOT NULL DEFAULT ''"
|
||||
)
|
||||
|
||||
_vcols = {r[1] for r in conn.execute("PRAGMA table_info(collection_views)").fetchall()}
|
||||
if "created_by" not in _vcols:
|
||||
conn.execute("ALTER TABLE collection_views ADD COLUMN created_by INTEGER")
|
||||
if "updated_at" not in _vcols:
|
||||
conn.execute("ALTER TABLE collection_views ADD COLUMN updated_at TIMESTAMP")
|
||||
|
||||
_cpcols = {r[1] for r in conn.execute("PRAGMA table_info(collection_pages)").fetchall()}
|
||||
if "cover_url" not in _cpcols:
|
||||
conn.execute("ALTER TABLE collection_pages ADD COLUMN cover_url TEXT DEFAULT ''")
|
||||
|
||||
|
||||
@register(11, "v5.8.0: reminder log + user timezones")
|
||||
def _migration_v58_calendar_reminders(conn: sqlite3.Connection) -> None:
|
||||
"""v5.8.0 — Calendrier & Rappels.
|
||||
|
||||
``reminder_log`` — dedup ledger: one row per (page, occurrence date) so
|
||||
a reminder fires exactly once even across restarts.
|
||||
``users.timezone`` — personal IANA timezone used for "today" in calendar
|
||||
views and reminder firing (empty = UTC).
|
||||
"""
|
||||
conn.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS reminder_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE,
|
||||
occurrence_date TEXT NOT NULL,
|
||||
fired_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(page_id, occurrence_date)
|
||||
)"""
|
||||
)
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_remlog_page ON reminder_log(page_id)"
|
||||
)
|
||||
|
||||
_ucols = {r[1] for r in conn.execute("PRAGMA table_info(users)").fetchall()}
|
||||
if "timezone" not in _ucols:
|
||||
conn.execute("ALTER TABLE users ADD COLUMN timezone TEXT NOT NULL DEFAULT ''")
|
||||
|
||||
|
||||
@register(12, "v5.8.0: enrich Meeting notes template")
|
||||
def _migration_v58_meeting_template(conn: sqlite3.Connection) -> None:
|
||||
"""v5.8.0 — the seeded 'Meeting notes' database template gains Agenda and
|
||||
Notes text properties. Only refreshed when the row still matches the old
|
||||
built-in schema (user edits are never clobbered)."""
|
||||
import json as _json
|
||||
row = conn.execute(
|
||||
"SELECT schema_json FROM database_templates WHERE name='Meeting notes'"
|
||||
).fetchone()
|
||||
if not row:
|
||||
return
|
||||
try:
|
||||
schema = _json.loads(row[0] or "[]")
|
||||
except (ValueError, TypeError):
|
||||
return
|
||||
names = [p.get("name") for p in schema]
|
||||
if "Agenda" in names or "Notes" in names:
|
||||
return
|
||||
if names != ["Title", "Date", "Attendees", "Status", "Action items"]:
|
||||
return # customised — leave alone
|
||||
idx = names.index("Action items")
|
||||
schema[idx:idx] = [
|
||||
{"name": "Agenda", "type": "text"},
|
||||
{"name": "Notes", "type": "text"},
|
||||
]
|
||||
conn.execute(
|
||||
"UPDATE database_templates SET schema_json=? WHERE name='Meeting notes'",
|
||||
(_json.dumps(schema),),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE database_templates SET description=? WHERE name='Meeting notes'",
|
||||
("Notes de réunion avec participants, agenda, notes et actions.",),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user