7.0 KiB
Calendar & Contacts API
logic.md
This document defines the COMPLETE business logic layer of the Calendar & Contacts API.
It explains:
- Permission enforcement
- Ownership rules
- Validation rules
- Recurrence engine behavior
- Reminder processing
- Availability calculation
- Booking logic
- Transaction boundaries
- Audit requirements
- Edge case handling
This file MUST be treated as the authoritative source for backend logic.
GLOBAL INVARIANTS
- All timestamps stored in UTC.
- All API timestamps are RFC3339 strings.
- Ownership is ALWAYS derived from auth context.
- Soft deletes are enforced everywhere.
- No endpoint may leak data across users.
- All mutations must write audit log entries.
- AUTHENTICATION & CONTEXT
Every authenticated request must result in a RequestContext struct:
RequestContext:
- user_id (uuid)
- auth_method ("jwt" | "api_key")
- scopes (if api_key)
JWT auth:
- Validate signature
- Validate expiration
- Extract user_id
API key auth:
- Hash provided key
- Lookup in api_keys
- Ensure revoked_at is NULL
- Load scopes
If neither provided → AUTH_REQUIRED
If invalid → AUTH_INVALID
- PERMISSION MODEL
Calendar roles:
- owner
- editor
- viewer
Permission matrix:
CALENDAR ACTIONS
- owner: full access
- editor: read calendar, CRUD events
- viewer: read-only
EVENT ACTIONS
- owner/editor: create/update/delete
- viewer: read
CONTACT ACTIONS
- Only owner of contacts can CRUD
BOOKING
- Only owner can create booking links
API KEY SCOPE ENFORCEMENT Each endpoint maps to required scope. Example:
- GET /calendars → calendars:read
- POST /events → events:write
- GET /contacts → contacts:read
If scope missing → FORBIDDEN
- USER LOGIC
REGISTER
- Email lowercase
- Must be unique
- Password >=10 chars
- Hash with bcrypt cost >=12
- Create default calendar
- Return tokens
DELETE USER
- Soft delete user
- Soft delete calendars
- Soft delete events
- Soft delete contacts
- Revoke API keys
- CALENDAR LOGIC
CREATE
- owner_id = authenticated user
- name required (1..80)
- color optional hex validation
LIST Return calendars where:
- owner_id = user_id OR
- calendar_members.user_id = user_id
Include role in response.
DELETE
- Only owner
- Soft delete calendar
- Soft delete all related events
SHARING
- Only owner can share
- Cannot share with self
- Upsert membership row
REMOVE MEMBER
- Only owner
- Cannot remove owner
- EVENT LOGIC
CREATE EVENT Validation:
- calendar exists
- user has editor or owner role
- title 1..140
- end_time > start_time
- timezone valid IANA
Time handling:
- Convert start_time to UTC
- Convert end_time to UTC
- Store original timezone string
Overlap rule:
- Overlap allowed by default
- Booking system enforces no-overlap
If recurrence_rule provided:
- Validate via rrule-go
- Store string
If reminders provided:
- Insert reminders
- Schedule jobs
UPDATE EVENT
- Same permission check
- Re-validate time constraints
- If recurrence changed → validate again
- Reschedule reminders
DELETE EVENT
- Soft delete
- RECURRENCE ENGINE
Recurring events are NOT pre-expanded.
Storage:
- recurrence_rule string on master event
Expansion occurs ONLY during:
- GET /events
- GET /events/{id}/occurrences
Algorithm:
For each event in DB where:
- event.start_time <= range_end
If no recurrence_rule:
- Include if intersects range
If recurrence_rule present:
-
Initialize rrule with DTSTART = event.start_time
-
Generate occurrences within [range_start, range_end]
-
For each occurrence:
- Check against exceptions
- Create virtual occurrence object
Occurrence response must include:
- is_occurrence = true
- occurrence_start_time
- occurrence_end_time
Exceptions table:
- event_id
- exception_date
- action ("skip")
If occurrence matches exception_date → skip
- REMINDER PROCESSING
Reminder scheduling rule:
For each reminder (minutes_before): trigger_time = event.start_time - minutes_before
If trigger_time > now:
- Enqueue Asynq job
Job payload:
- event_id
- reminder_id
- user_id
Worker logic:
- Load event
- If event.deleted_at != NULL → abort
- Send notification (webhook/email placeholder)
- Retry on failure with exponential backoff
On event update:
- Cancel old reminder jobs (if supported)
- Recompute schedule
- CONTACT LOGIC
Contacts are strictly per-user.
CREATE
- Must have at least one identifying field
- email validated if present
SEARCH
-
Case-insensitive match on:
- first_name
- last_name
- company
DELETE
- Soft delete
- AVAILABILITY LOGIC
Endpoint requires:
- calendar_id
- start
- end
Permission:
- viewer or higher
Busy condition: An event is busy if:
(event.start_time < range_end) AND (event.end_time > range_start)
Recurring events:
- Expand occurrences
- Apply same intersection rule
Return busy blocks sorted by start.
- BOOKING SYSTEM LOGIC
CREATE BOOKING LINK
- Only owner
- Generate secure random token
- Store configuration
PUBLIC AVAILABILITY
- No auth
- Load booking link
- Validate active
- Compute working-hour windows
- Subtract busy blocks
- Apply buffer_minutes before/after events
RESERVATION
- Begin DB transaction
- Re-check slot availability with overlap query
- If conflict → ROLLBACK + CONFLICT
- Insert event
- Commit
Overlap query inside transaction:
SELECT 1 FROM events WHERE calendar_id = ? AND deleted_at IS NULL AND start_time < slot_end AND end_time > slot_start FOR UPDATE
This ensures race safety.
- CURSOR PAGINATION
Cursor format: base64url(last_sort_time|last_id)
Decoding:
- Split by pipe
- Use as tuple comparison
Events sorting: ORDER BY start_time ASC, id ASC
Contacts sorting: ORDER BY created_at ASC, id ASC
Limit enforcement:
- Default 50
- Max 200
- AUDIT LOG LOGIC
Every mutation writes:
entity_type entity_id action user_id timestamp
Actions examples:
- CREATE_EVENT
- UPDATE_EVENT
- DELETE_EVENT
- SHARE_CALENDAR
- DELETE_CONTACT
Audit writes MUST NOT fail main transaction. If audit insert fails:
- Log error
- Continue response
- ERROR RULES
AUTH_REQUIRED → no credentials AUTH_INVALID → invalid token FORBIDDEN → permission denied NOT_FOUND → entity not found or not accessible VALIDATION_ERROR → invalid input CONFLICT → overlap or duplicate INTERNAL → unexpected error
Never expose internal DB errors directly.
- TRANSACTION RULES
Transactions REQUIRED for:
- Booking reservation
- Event creation with reminders
- Event update affecting reminders
- Deleting calendar (cascade soft delete)
Service layer must manage transactions. Repository layer must not auto-commit.
- PERFORMANCE REQUIREMENTS
All list endpoints:
- Must use indexed columns
- Must use pagination
- Must avoid N+1 queries
Recurring expansion must be bounded by requested range.
Maximum recurrence expansion window allowed per request:
- 1 year (recommended safeguard)
If range exceeds safeguard → VALIDATION_ERROR
This is the authoritative backend logic specification.