8 Commits

Author SHA1 Message Date
f54ab5079e Fix QA review findings: C-01, C-02, W-01, W-02, W-04, S-01, S-02, S-03
C-01: Remove nginx rate limit on event invitations endpoint — was
      blocking GET (invitee list) on rapid event switching. Backend
      already caps at 20 invitations per event with connection validation.

C-02: respondingRef uses string prefixes (conn-, cal-, event-) instead
      of fragile numeric offsets (+100000/+200000) to prevent collisions.

W-01: get_accessible_event_scope combined into single UNION ALL query
      (3 DB round-trips → 1) for calendar IDs + invitation IDs.

W-02: Dashboard and upcoming endpoints now include is_invited,
      invitation_status, and display_calendar_id on event items.

W-04: LeaveEventDialog closes on error (.finally) instead of staying
      open when mutation rejects.

S-01: Migration 055 FK constraint gets explicit name for consistency.

S-02: InviteSearch dropdown dismisses on blur (150ms delay for clicks).

S-03: Display calendar picker shows only owned calendars, not shared.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 20:27:01 +08:00
df857a5719 Fix QA findings: flush before notify, dedup RSVP, sa_false, validation
- C-02: flush invitations before creating notifications so invitation_id
  is available in notification data; eliminates extra pending fetch
- C-03: skip RSVP notification when status hasn't changed
- C-01: add defensive comments on update/delete endpoints
- W-01: add ge=1, le=2147483647 per-element validation on user_ids
- W-04: deduplicate invited_event_ids query via get_invited_event_ids()
- W-06: replace Python False with sa_false() in or_() clauses
- Frontend: extract resolveInvitationId helper, prefer data.invitation_id

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 14:01:15 +08:00
8652c9f2ce Implement event invitation feature (invite, RSVP, per-occurrence override, leave)
Full-stack implementation of event invitations allowing users to invite connected
contacts to calendar events. Invitees can respond Going/Tentative/Declined, with
per-occurrence overrides for recurring series. Invited events appear on the invitee's
calendar with a Users icon indicator. LeaveEventDialog replaces delete for invited events.

Backend: Migration 054 (2 tables + notification types), EventInvitation model with
lazy="raise", service layer, dual-router (events + event-invitations), cascade on
disconnect, events/dashboard queries extended with OR for invited events.

Frontend: Types, useEventInvitations hook, InviteeSection (view list + RSVP buttons +
invite search), LeaveEventDialog, event invite toast with 3 response buttons, calendar
eventContent render with Users icon for invited events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 02:47:27 +08:00
a2c1058f9c Fix QA findings: single UNION query, weekly validation, nginx docs
W-01: Consolidate get_accessible_calendar_ids to single UNION query
instead of two separate DB round-trips.
W-02: Document that nginx rate limit on /api/events applies to all
methods (30r/m generous enough for GET polling at 2r/m).
W-03: Add weekly rule validation for consistency with other rule types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 01:46:11 +08:00
be1fdc4551 Calendar backend optimisations: safety caps, shared calendar fix, query consolidation
Phase 1: Recurrence safety — MAX_OCCURRENCES=730 hard cap, adaptive 90-day
horizon for daily events (interval<7), RecurrenceRule cross-field validation,
ID bounds on location_id/calendar_id schemas.

Phase 2: Dashboard correctness — shared calendar events now included in
/dashboard and /upcoming via get_accessible_calendar_ids helper. Project stats
consolidated into single GROUP BY query (saves 1 DB round-trip).

Phase 3: Write performance — bulk db.add_all() for child events, removed
redundant SELECT in this_and_future delete path.

Phase 4: Frontend query efficiency — staleTime: 30_000 on calendar events
query eliminates redundant refetches on mount/view switch. Backend LIMIT 2000
safety guard on events endpoint.

Phase 5: Rate limiting — nginx limit_req zone on /api/events (30r/m) to
prevent DB flooding via recurrence amplification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 01:31:48 +08:00
846019d5c1 Phase 3: Backend queries and indexes optimization
- AW-1: Add composite index on calendar_members(user_id, status) for the
  hot shared-calendar polling query
- AS-6: Add composite index on ntfy_sent(user_id, sent_at) for dedup lookups
- AW-5: Combine get_user_permission into single LEFT JOIN query instead of
  2 sequential queries (called twice per event edit)
- AC-5: Batch cascade_on_disconnect — single GROUP BY + bulk UPDATE instead
  of N per-calendar checks when a connection is severed
- AW-6: Collapse admin dashboard 5 COUNT queries into single conditional
  aggregation using COUNT().filter()
- AC-3: Cache get_current_settings in request.state to avoid redundant
  queries when multiple dependencies need settings in the same request

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:08:45 +08:00
206144d20d Fix 2 pentest findings: unlock permission check + permanent lock preservation
SC-01: unlock_event now verifies caller has access to the calendar before
revealing lock state. Previously any authenticated user could probe event
existence via 404/204/403 response differences.

SC-02: acquire_lock no longer overwrites permanent locks. If the owner holds
a permanent lock and clicks Edit, the existing lock is returned as-is instead
of being downgraded to a 5-minute temporary lock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:37:05 +08:00
e6e81c59e7 Phase 2: Shared calendars backend core + QA fixes
Router: invite/accept/reject flow, membership CRUD, event locking
(timed + permanent), sync endpoint, local color override.
Services: permission hierarchy, atomic lock acquisition, disconnect cascade.
Events: shared calendar scoping, permission/lock enforcement, updated_by tracking.
Admin: sharing-stats endpoint. nginx: rate limits for invite + sync.

QA fixes: C-01 (read-only invite gate), C-02 (updated_by in this_and_future),
W-01 (pre-commit response build), W-02 (owned calendar short-circuit),
W-03 (sync calendar_ids cap), W-04 (N+1 owner name batch fetch).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 04:46:17 +08:00