Night Routine Scheduler Architecture¶
1. Project Structure¶
graph TD
A[Main Application] --> B[Config Manager]
A --> C[Scheduling Engine]
A --> D[Calendar Service]
A --> E[Fairness Tracker]
A --> W[Web UI]
A --> WH[Webhook Handler]
A --> DB[Database Manager]
A --> LG[Logging Service]
W --> VH[View Helpers]
B --> F[routine.toml]
B --> M[Environment Variables]
C --> G[Schedule Generator]
D --> H[Google Calendar API]
E --> I[State Storage]
W --> J[OAuth Handler]
W --> K[Calendar Selection]
W --> HC[Home Handler]
WH --> DB
WH --> C
DB --> MG[Database Migrations]
LG --> ZL[Zerolog Library]
HC --> VH
HC --> C
J --> I
K --> I
I --> L[(SQLite DB)]
MG --> L
2. Components Breakdown¶
2.1 Configuration Manager¶
- Uses TOML format for application configuration
- Uses environment variables for sensitive data and environment settings (e.g.,
ENVfor logging format) - Configuration sources:
# routine.toml - Application configuration
[parents]
parent_a = "Parent1"
parent_b = "Parent2"
[availability]
parent_a_unavailable = ["Wednesday"]
parent_b_unavailable = ["Monday"]
[schedule]
update_frequency = "weekly"
look_ahead_days = 30
past_event_threshold_days = 5 # How many days in the past to accept event changes (default: 5)
[service]
state_file = "data/state.db"
log_level = "info" # Logging level
# manual_sync_on_startup = true # Optional: Perform sync on startup (defaults to true). Set to false to disable.
Environment Variables¶
# Environment Variables - Configuration
GOOGLE_OAUTH_CLIENT_ID=your-client-id # OAuth2 Configuration
GOOGLE_OAUTH_CLIENT_SECRET=your-client-secret
PORT=8080 # Service Configuration
CONFIG_FILE=configs/routine.toml # Path to TOML config file
APP_URL=http://localhost:8080 # (Optional) Override application URL
ENV=development # (Optional) "production" for JSON logs, otherwise pretty console logs
2.2 Scheduling Engine¶
flowchart LR
A[Load Config] --> B[Check Availability]
B --> C[Check History]
C --> D[Generate Schedule]
D --> E[Apply Fairness Rules]
E --> F[Produce Final Schedule]
2.3 Google Calendar Integration¶
sequenceDiagram
participant UI as Web UI
participant App
participant Auth
participant DB as SQLite
participant GCal
Note over App: Load OAuth config from env
UI->>Auth: OAuth2 Flow
Auth-->>UI: Token
UI->>DB: Store Token
UI->>GCal: List Calendars
GCal-->>UI: Available Calendars
UI->>DB: Store Selected Calendar
loop Weekly
App->>DB: Get Token & Calendar
App->>App: Generate Schedule
App->>GCal: Create/Update Events
end
2.4 Web Interface¶
- OAuth2 authentication flow using environment variables
- Calendar selection UI
- Components:
- Home page with connection status
- OAuth callback handler
- Calendar selection page
- Success/error notifications
2.5 State Storage (SQLite)¶
- Database schema managed through migrations
- Migrations stored as embedded SQL files in the application
- Tables:
-- Night routine assignments
assignments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_name TEXT NOT NULL,
assignment_date TEXT NOT NULL,
override BOOLEAN DEFAULT FALSE,
google_calendar_event_id TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
-- OAuth tokens
oauth_tokens (
id INTEGER PRIMARY KEY, -- Should always be 1
token_data JSONB NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
-- Calendar settings
calendar_settings (
id INTEGER PRIMARY KEY, -- Should always be 1
calendar_id TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
-- Notification channels
notification_channels (
id TEXT PRIMARY KEY,
resource_id TEXT NOT NULL,
calendar_id TEXT NOT NULL,
expiration DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
2.6 Fairness Tracker¶
- Maintains state in SQLite to track:
- Historical assignments
- Consecutive assignments
- Total count per parent
- Uses assignment data for scheduling decisions
2.7 Webhook Handler (Google Calendar Integration)¶
The application implements a webhook handler at /api/webhook/calendar specifically designed to process Google Calendar push notifications. This allows the system to react dynamically to manual changes made directly in the shared Google Calendar.
Workflow:
- Notification Reception: Google Calendar sends a push notification to the endpoint when a change occurs in the subscribed calendar.
- Authentication & Validation:
- The handler verifies the
X-Goog-Channel-IDandX-Goog-Resource-IDheaders against the stored notification channel details (retrieved from thenotification_channelstable in SQLite). This ensures the notification is legitimate and originates from the expected Google Calendar subscription. - If the channel ID or resource ID doesn't match, the request is rejected (HTTP 400).
- It checks the
X-Goog-Resource-Stateheader. If it'ssync, it's an initial synchronization message, and the handler simply acknowledges it (HTTP 200) without further processing.
- The handler verifies the
- Channel Renewal Check: The handler checks the expiration time of the notification channel. If it's nearing expiration (e.g., within 7 days), it proactively attempts to renew the subscription with Google Calendar using the stored refresh token and calendar ID.
- Fetch Updated Events: For actual change notifications (
X-Goog-Resource-Stateis notsync), the handler uses the Google Calendar API to fetch events that have been updated recently (e.g., within the last 2 minutes). It uses theupdatedMinparameter for efficiency. - Event Processing Loop:
- For each updated event retrieved:
- Ownership Check: It verifies the event belongs to this application by checking for a specific private extended property (e.g.,
private["app"] == "night-routine"). Events without this property are ignored. - Extract Parent: It parses the event summary (expected format:
"[ParentName] 🌃👶Routine") to extract the assigned parent's name. - Find Local Assignment: It queries the
assignmentstable using thegoogle_calendar_event_idto find the corresponding local record. - Change Detection: It compares the extracted parent name with the parent name stored in the local assignment record.
- Date Check: It ensures the assignment date is within the configurable past event threshold (default: 5 days). The threshold is configured via
past_event_threshold_daysin the[schedule]section ofroutine.toml. Overrides for assignments older than this threshold are rejected with a warning logged. - Update Local Assignment: If the parent name has changed and the assignment is within the threshold, it updates the
parent_nameand sets theoverrideflag totruein theassignmentstable for that record.
- Trigger Schedule Recalculation: If any local assignment was updated due to an override, the handler triggers the
Schedulercomponent.- The scheduler regenerates the schedule starting from the date of the earliest overridden assignment up to the previously calculated end date.
- This recalculation respects the new override(s) and applies fairness rules to the subsequent, non-overridden days.
- Sync Recalculated Schedule: The newly generated portion of the schedule is synced back to Google Calendar by the
CalendarService, updating or creating events as necessary. - Acknowledge Notification: Finally, the handler responds to the original Google Calendar push notification with an HTTP 200 OK status.
Key Interactions:
- Database Manager: Reads notification channel details, reads/writes assignment records.
- Token Manager: Obtains a valid OAuth2 token to interact with the Google Calendar API.
- Calendar Service: Renews notification channels, fetches updated events, syncs recalculated schedule.
- Scheduler: Retrieves assignments by event ID, updates assignments, triggers schedule regeneration.
- Config Manager: Provides Google API credentials.
2.8 Logging Service¶
- Centralized logging configuration in
internal/logging. - Uses zerolog for structured, leveled logging.
- Log level configured via
log_levelinroutine.toml. - Log format determined by
ENVenvironment variable: development(default): Human-readable console output.production: JSON output to stdout.- Provides helper functions (
GetLogger) to create component-specific loggers with context.
3. Implementation Plan¶
-
Phase 1: Core Structure
-
Project initialization with Go 1.24
- Environment variables configuration
- Basic TOML configuration setup
- Command-line interface
- Docker configuration
-
SQLite schema setup
-
Phase 2: Web Interface
-
OAuth2 flow with environment variables
- Calendar selection UI
- Token storage in SQLite
-
Basic web server setup
-
Phase 3: Scheduling Logic
-
Schedule generation algorithm
- Fairness rules implementation
- State management
-
Database interactions
-
Phase 4: Google Calendar Integration
-
OAuth2 token management
- Calendar API integration
- Event management
-
Error handling
-
Phase 5: Service Mode & Logging
-
Background service implementation
- Scheduling loop
- Structured logging implementation (zerolog)
- Health checks
4. Technical Specifications¶
Technology Stack:¶
- Go 1.24
- Libraries:
github.com/BurntSushi/tomlfor TOML parsinggoogle.golang.org/api/calendar/v3for Google Calendargolang.org/x/oauth2for OAuth2 handling- Built-in
database/sqlwithgithub.com/mattn/go-sqlite3for state management github.com/golang-migrate/migrate/v4for database migrationshtml/templatefor web UIgithub.com/rs/zerologfor logging
Security Considerations:¶
- OAuth2 credentials stored in environment variables
- Tokens stored securely in SQLite database
- HTTPS recommended for production deployments
- Proper error handling for credential issues
Database Management:¶
- Database schema versioned and managed through migrations
- Migrations embedded in the application binary using Go 1.16+ embed package
- Migration files organized in numbered pairs (up/down) for versioning
- Automatic migration at application startup
- Support for both schema creation and updates