first commit
Made-with: Cursor
This commit is contained in:
501
about/logic.md
Normal file
501
about/logic.md
Normal file
@@ -0,0 +1,501 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user