502 lines
7.0 KiB
Markdown
502 lines
7.0 KiB
Markdown
# 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
|
|
|
|
1. All timestamps stored in UTC.
|
|
2. All API timestamps are RFC3339 strings.
|
|
3. Ownership is ALWAYS derived from auth context.
|
|
4. Soft deletes are enforced everywhere.
|
|
5. No endpoint may leak data across users.
|
|
6. All mutations must write audit log entries.
|
|
|
|
---
|
|
|
|
1. 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
|
|
|
|
---
|
|
|
|
2. 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
|
|
|
|
---
|
|
|
|
3. 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
|
|
|
|
---
|
|
|
|
4. 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
|
|
|
|
---
|
|
|
|
5. 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
|
|
|
|
---
|
|
|
|
6. 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
|
|
|
|
---
|
|
|
|
7. 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
|
|
|
|
---
|
|
|
|
8. 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
|
|
* email
|
|
* company
|
|
|
|
DELETE
|
|
|
|
* Soft delete
|
|
|
|
---
|
|
|
|
9. 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.
|
|
|
|
---
|
|
|
|
10. 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.
|
|
|
|
---
|
|
|
|
11. 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
|
|
|
|
---
|
|
|
|
12. 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
|
|
|
|
---
|
|
|
|
13. 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.
|
|
|
|
---
|
|
|
|
14. 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.
|
|
|
|
---
|
|
|
|
15. 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.
|