- Public/private: toggle is_public via PUT /calendars/{id}; generate/clear
public_token and return ical_url when public
- Public feed: GET /cal/{token}/feed.ics (no auth) for subscription in
Google/Apple/Outlook calendars
- Full iCal export: use golang-ical; VALARM, ATTENDEE, all-day (VALUE=DATE),
RRULE, DTSTAMP, CREATED, LAST-MODIFIED
- Full iCal import: parse TZID, VALUE=DATE, VALARM, ATTENDEE, RRULE
- Import from URL: POST /calendars/import-url with calendar_id + url
- Migration: unique index on public_token, calendar_subscriptions table
- Config: BASE_URL for ical_url; Calendar model + API: ical_url field
- Docs: OpenAPI, llms.txt, README, SKILL.md, about/overview
Made-with: Cursor
284 lines
9.1 KiB
Markdown
284 lines
9.1 KiB
Markdown
# Calendar & Contacts API
|
|
|
|
A production-grade REST API for calendar management, event scheduling, contacts, availability queries, and public booking links. Built with Go, PostgreSQL, and designed for human users, AI agents, and programmatic automation.
|
|
|
|
## Features
|
|
|
|
- **Calendars** - Create, share, and manage multiple calendars with role-based access (owner/editor/viewer)
|
|
- **Events** - Full CRUD with recurring events (RFC 5545 RRULE), reminders, attendees, tags, and attachments
|
|
- **Contacts** - Personal contact management with search
|
|
- **Availability** - Query busy/free time across calendars
|
|
- **Booking Links** - Public scheduling pages with configurable working hours, duration, and buffer time
|
|
- **ICS Import/Export** - Full RFC 5545 iCalendar support with VALARM, ATTENDEE, TZID, all-day events
|
|
- **Public iCal Feeds** - Make calendars public and subscribe via iCal URL in any calendar app
|
|
- **iCal URL Import** - Import events from any external iCal feed URL
|
|
- **Dual Auth** - JWT tokens for interactive use, scoped API keys for agents and automation
|
|
- **Background Jobs** - Reminder notifications via Redis + Asynq (optional)
|
|
|
|
## Tech Stack
|
|
|
|
| Component | Technology |
|
|
|-----------|-----------|
|
|
| Language | Go 1.24 |
|
|
| Router | Chi |
|
|
| Database | PostgreSQL 15+ |
|
|
| Query Layer | sqlc |
|
|
| Migrations | golang-migrate |
|
|
| Auth | JWT (golang-jwt/jwt/v5) + bcrypt |
|
|
| Recurrence | rrule-go (RFC 5545) |
|
|
| Background Jobs | Asynq (Redis) |
|
|
| API Docs | OpenAPI 3.1.0 + Swagger UI |
|
|
|
|
## Quick Start
|
|
|
|
### Prerequisites
|
|
|
|
- Go 1.24+
|
|
- PostgreSQL 15+
|
|
- Redis (optional, for background reminder jobs)
|
|
|
|
### Setup
|
|
|
|
1. Clone the repository and navigate to the project directory.
|
|
|
|
2. Copy the environment file and configure it:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
3. Edit `.env` with your database credentials:
|
|
|
|
```env
|
|
DATABASE_URL=postgres://calendarapi:password@localhost:5432/calendarapi?sslmode=disable
|
|
JWT_SECRET=your-secret-key-change-me
|
|
SERVER_PORT=3019
|
|
ENV=development
|
|
# REDIS_ADDR=localhost:6379 # uncomment to enable background jobs
|
|
```
|
|
|
|
4. Create the database:
|
|
|
|
```bash
|
|
createdb calendarapi
|
|
```
|
|
|
|
5. Run the server (migrations run automatically on startup):
|
|
|
|
```bash
|
|
go run cmd/server/main.go
|
|
```
|
|
|
|
The server starts on `http://localhost:3019`. Swagger UI is available at `http://localhost:3019/docs`.
|
|
|
|
## API Documentation
|
|
|
|
| Resource | URL |
|
|
|----------|-----|
|
|
| Swagger UI | http://localhost:3019/docs |
|
|
| OpenAPI Spec | http://localhost:3019/openapi.json |
|
|
| LLM Reference | [llms.txt](llms.txt) |
|
|
| Agent Skill | [SKILL.md](SKILL.md) |
|
|
|
|
## Authentication
|
|
|
|
### JWT (for users)
|
|
|
|
Register or login to receive an access token (15 min) and refresh token:
|
|
|
|
```bash
|
|
# Register
|
|
curl -X POST http://localhost:3019/auth/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email": "user@example.com", "password": "securepassword123", "timezone": "UTC"}'
|
|
|
|
# Login
|
|
curl -X POST http://localhost:3019/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email": "user@example.com", "password": "securepassword123"}'
|
|
```
|
|
|
|
Use the access token in subsequent requests:
|
|
|
|
```
|
|
Authorization: Bearer <access_token>
|
|
```
|
|
|
|
### API Keys (for agents)
|
|
|
|
Create a scoped API key for long-lived programmatic access:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:3019/api-keys \
|
|
-H "Authorization: Bearer <access_token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "my-agent",
|
|
"scopes": {
|
|
"calendars": ["read", "write"],
|
|
"events": ["read", "write"],
|
|
"contacts": ["read", "write"],
|
|
"availability": ["read"],
|
|
"booking": ["write"]
|
|
}
|
|
}'
|
|
```
|
|
|
|
Use the returned token in subsequent requests:
|
|
|
|
```
|
|
X-API-Key: <token>
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
### Auth
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|--------|----------|------|-------------|
|
|
| POST | `/auth/register` | None | Create account |
|
|
| POST | `/auth/login` | None | Login |
|
|
| POST | `/auth/refresh` | None | Refresh JWT tokens |
|
|
| POST | `/auth/logout` | Yes | Revoke refresh token |
|
|
| GET | `/auth/me` | Yes | Get current user |
|
|
|
|
### Users
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/users/me` | Get profile |
|
|
| PUT | `/users/me` | Update profile |
|
|
| DELETE | `/users/me` | Delete account |
|
|
|
|
### API Keys
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api-keys` | Create API key |
|
|
| GET | `/api-keys` | List API keys |
|
|
| DELETE | `/api-keys/{id}` | Revoke API key |
|
|
|
|
### Calendars
|
|
|
|
| Method | Endpoint | Scope | Description |
|
|
|--------|----------|-------|-------------|
|
|
| GET | `/calendars` | calendars:read | List calendars |
|
|
| POST | `/calendars` | calendars:write | Create calendar |
|
|
| GET | `/calendars/{id}` | calendars:read | Get calendar |
|
|
| PUT | `/calendars/{id}` | calendars:write | Update calendar |
|
|
| DELETE | `/calendars/{id}` | calendars:write | Delete calendar |
|
|
| POST | `/calendars/{id}/share` | calendars:write | Share calendar |
|
|
| GET | `/calendars/{id}/members` | calendars:read | List members |
|
|
| DELETE | `/calendars/{id}/members/{userID}` | calendars:write | Remove member |
|
|
|
|
### Events
|
|
|
|
| Method | Endpoint | Scope | Description |
|
|
|--------|----------|-------|-------------|
|
|
| GET | `/events` | events:read | List events in time range |
|
|
| POST | `/events` | events:write | Create event |
|
|
| GET | `/events/{id}` | events:read | Get event |
|
|
| PUT | `/events/{id}` | events:write | Update event |
|
|
| DELETE | `/events/{id}` | events:write | Delete event |
|
|
| POST | `/events/{id}/reminders` | events:write | Add reminders |
|
|
| DELETE | `/events/{id}/reminders/{reminderID}` | events:write | Remove reminder |
|
|
| POST | `/events/{id}/attendees` | events:write | Add attendees |
|
|
| PUT | `/events/{id}/attendees/{attendeeID}` | events:write | Update RSVP |
|
|
| DELETE | `/events/{id}/attendees/{attendeeID}` | events:write | Remove attendee |
|
|
|
|
### Contacts
|
|
|
|
| Method | Endpoint | Scope | Description |
|
|
|--------|----------|-------|-------------|
|
|
| GET | `/contacts` | contacts:read | List contacts |
|
|
| POST | `/contacts` | contacts:write | Create contact |
|
|
| GET | `/contacts/{id}` | contacts:read | Get contact |
|
|
| PUT | `/contacts/{id}` | contacts:write | Update contact |
|
|
| DELETE | `/contacts/{id}` | contacts:write | Delete contact |
|
|
|
|
### Availability
|
|
|
|
| Method | Endpoint | Scope | Description |
|
|
|--------|----------|-------|-------------|
|
|
| GET | `/availability` | availability:read | Get busy blocks for a calendar |
|
|
|
|
### Booking
|
|
|
|
| Method | Endpoint | Auth | Description |
|
|
|--------|----------|------|-------------|
|
|
| POST | `/calendars/{id}/booking-link` | booking:write | Create booking link |
|
|
| GET | `/booking/{token}/availability` | None | Get available slots |
|
|
| POST | `/booking/{token}/reserve` | None | Reserve a slot |
|
|
|
|
### ICS
|
|
|
|
| Method | Endpoint | Scope | Description |
|
|
|--------|----------|-------|-------------|
|
|
| GET | `/calendars/{id}/export.ics` | calendars:read | Export as ICS |
|
|
| POST | `/calendars/import` | calendars:write | Import ICS file |
|
|
| POST | `/calendars/import-url` | calendars:write | Import from iCal URL |
|
|
| GET | `/cal/{token}/feed.ics` | None (public) | Public iCal feed |
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
calendarapi/
|
|
cmd/server/main.go Entry point
|
|
internal/
|
|
api/
|
|
routes.go Route definitions
|
|
handlers/ HTTP handlers
|
|
openapi/ OpenAPI spec files + Swagger UI
|
|
auth/ JWT manager
|
|
config/ Environment config
|
|
middleware/ Auth, rate limiting, scope enforcement
|
|
models/ Domain models
|
|
repository/ sqlc-generated database queries
|
|
scheduler/ Asynq background job scheduler
|
|
service/ Business logic layer
|
|
utils/ Shared utilities
|
|
migrations/ SQL migration files
|
|
sqlc/ sqlc configuration
|
|
about/ Design specifications
|
|
overview.md Architecture and data model
|
|
logic.md Business logic rules
|
|
details.md Endpoint contracts
|
|
SKILL.md AI agent integration guide
|
|
llms.txt LLM-optimized API reference
|
|
.env.example Environment template
|
|
go.mod
|
|
go.sum
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Client (Web / Mobile / Agent)
|
|
|
|
|
HTTP REST API (Chi router)
|
|
|
|
|
Middleware (Auth, Rate Limiting, Scope Enforcement)
|
|
|
|
|
Handler Layer (Parse request, validate, return JSON)
|
|
|
|
|
Service Layer (Business logic, permissions, transactions)
|
|
|
|
|
Repository Layer (sqlc queries)
|
|
|
|
|
PostgreSQL
|
|
```
|
|
|
|
## Design Principles
|
|
|
|
- **Stateless** - JWT or API key on every request, no server-side sessions
|
|
- **UTC everywhere** - All timestamps stored in UTC, original timezone preserved
|
|
- **Soft deletes** - No data is permanently destroyed
|
|
- **Strict ownership** - Ownership derived from auth context, never from client input
|
|
- **Cursor pagination** - Efficient, consistent pagination on all list endpoints
|
|
- **Scoped API keys** - Fine-grained permission control for agent access
|
|
- **Race-safe booking** - Database transactions with row locking prevent double-booking
|
|
|
|
## License
|
|
|
|
MIT
|