Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ptshen/timeful-plus/llms.txt

Use this file to discover all available pages before exploring further.

Timeful is a classic three-tier web application: a Vue 2 single-page app communicates with a Go REST API, which in turn reads and writes to MongoDB. In the Docker Compose setup the three tiers run as isolated containers on a private bridge network; the frontend port (3002) and the MongoDB port (27017) are published to the host, while the backend API port is internal-only. Understanding this layout is useful whether you are debugging a deployment, contributing a feature, or integrating with the API.

Docker Architecture

The default docker-compose.yml defines three services that communicate over the timeful-network bridge:
Host machine
├── port 3002 ──► timeful-frontend  (Nginx, port 80 internal)
│                      │
│                      │ proxy /api/* → http://backend:3002
│                      ▼
│                 timeful-backend   (Go/Gin, port 3002, internal only)
│                      │
│                      │ mongodb://mongodb:27017
│                      ▼
└── port 27017 ─► timeful-mongodb   (MongoDB 6.0, port 27017 published to host)
In the Docker Compose layout, the backend’s port 3002 is declared with expose (container-internal only), while the frontend binds to 0.0.0.0:3002 on the host and MongoDB binds to 0.0.0.0:27017. The Go server detects the absence of ../frontend/dist at startup and switches to API-only mode, returning a JSON 404 for any non-API route.

Container Details

  • Image: mongo:6.0
  • Published port: 27017:27017 (host port 27017 maps to the MongoDB container)
  • Database name: schej-it
  • Volumes: mongodb_data:/data/db, mongodb_config:/data/configdb
  • Health check: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet with a 10-second interval and 5 retries
  • Security: Runs as UID/GID 999 (the official MongoDB user), drops all Linux capabilities except CHOWN, SETGID, and SETUID, and sets no-new-privileges:true
  • Image (GHCR): ghcr.io/lillenne/timeful.app/backend:latest
  • Internal port: 3002 (exposed only within timeful-network)
  • Depends on: mongodb (waits for service_healthy)
  • Gin mode: release (set via GIN_MODE environment variable)
  • Logs: Persisted to the backend_logs volume at /app/logs
  • Security: Runs as UID/GID 1000, drops all Linux capabilities, sets no-new-privileges:true
  • Image (GHCR): ghcr.io/lillenne/timeful.app/frontend:latest
  • Published port: 3002:80 — host port 3002 maps to Nginx on port 80
  • Nginx role: Serves the compiled Vue 2 dist/ bundle and reverse-proxies all /api/* requests to http://backend:3002/api/*
  • Depends on: backend
  • Config injection: config.js is bind-mounted into /app/public/config.js at runtime, allowing OAuth client IDs to be changed without rebuilding the image

Backend Structure (/server)

The Go backend is organized into focused packages. The entry point is main.go, which sets up the Gin router, registers all route groups, connects to MongoDB, and starts the HTTP server on port 3002.

Package Reference

routes/

One file per resource — auth.go, events.go, user.go, folders.go, analytics.go, stripe.go. Each file exports an Init* function called from main.go that registers all HTTP handlers for that resource under the /api prefix.

services/

External integrations. The calendar/ sub-package defines a CalendarProvider interface implemented by Google, Outlook, and Apple adapters. Async helpers (GetCalendarEventsAsync, GetCalendarListAsync) use Go channels so multiple calendar accounts are fetched concurrently. The auth/ sub-package handles OAuth token refresh logic.

models/

MongoDB document schemas as Go structs with both json and bson struct tags. Key models: Event (with EventType constant specific_dates | dow | group), User, Response, Attendee, Folder, SignUpBlock, and SignUpResponse. The SignUpBlock struct carries an optional Capacity *int field — the Columbia Math Department addition.

db/

Database layer with global collection references initialized in init.go. Helper functions (GetEventByEitherId, GetUserById, etc.) are the single access point for MongoDB queries, keeping all BSON logic out of route handlers.

middleware/

AuthRequired() is a Gin middleware that reads the userId key from the session cookie, looks up the user in MongoDB, and either injects authUser into the Gin context or aborts with HTTP 401. Route groups that require authentication wrap themselves with this middleware.

utils/

Stateless helper functions split across utils.go (general), array_utils.go, db_utils.go, mail_utils.go, and request_utils.go. The custom errs/ package defines typed error constants (NotSignedIn, UserDoesNotExist, etc.) that middleware and handlers use for consistent error responses.

API Routes

All routes are mounted under the /api prefix. The table below lists every route group registered in main.go:
GroupFileDescription
/api/authroutes/auth.goGoogle OAuth sign-in/sign-out, Microsoft OAuth, session management
/api/userroutes/user.goFetch and update the authenticated user’s profile and calendar accounts
/api/eventsroutes/events.goCreate, read, update, delete availability polls and sign-up forms
/api/user/foldersroutes/folders.goOrganize events into named folders
/api/analyticsroutes/analytics.goInternal usage metrics endpoints
/api/striperoutes/stripe.goStripe webhook and subscription management (no-op when SELF_HOSTED_PREMIUM=true)
Interactive documentation for all endpoints is served by Swagger UI at /swagger/index.html. The spec is generated from Go doc comments and kept in the server/docs/ package.

Swagger UI

The Swagger handler is registered in main.go as:
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
Navigate to http://localhost:3002/swagger/index.html when the backend is running to explore and test every API endpoint with the built-in “Try it out” feature.

Frontend Structure (/frontend/src)

The Vue 2 SPA follows a conventional views/ + components/ split with Vuex for state and Vue Router for navigation.

views/ — Route-Level Pages

Each file in views/ corresponds to a top-level Vue Router route:
FileRoutePurpose
Home.vue/Dashboard listing the authenticated user’s events and groups
Event.vue/e/:eventIdAvailability poll — respondents fill in their times; owner sees the heatmap
Group.vue/g/:groupIdAvailability group — real-time calendar overlay for all group members
Settings.vue/settingsUser profile, connected calendar accounts, notification preferences
Landing.vue(public)Marketing landing page shown to unauthenticated visitors
Auth.vue/authOAuth callback handler for Microsoft sign-in
SignUp.vue/s/:eventIdSign-up form view with named time blocks and capacity indicators
Friends.vue/friendsManage friend/contact relationships

components/ — Reusable UI Components

Components are organized into feature-specific sub-directories:
DirectoryContents
event/Availability grid, time-slot selector, response heatmap, attendee list
groups/Group member list, real-time availability overlay
home/Event cards, quick-create dialogs, folder navigation
settings/Calendar account connection, notification toggles
sign_up_form/Block editor, capacity counter, respondent list
schedule_overlap/Cross-event schedule comparison tools
calendar_permission_dialogs/Step-by-step OAuth permission request dialogs
landing/Feature marketing sections for the public landing page
pricing/Premium feature comparison table
general/Shared primitives: Header.vue, Footer.vue, Logo.vue, Tooltip.vue, etc.

State & Routing

  • Vuex store (/frontend/src/store/) — holds the authenticated user object, event lists, and UI state (active dialog, snackbar queue). Components read from the store via mapState and dispatch actions for data fetching.
  • Vue Router (/frontend/src/router/) — client-side SPA routing. Nginx is configured to serve index.html for all non-asset paths so deep links work after a hard refresh.
  • API services (/frontend/src/utils/services/) — thin wrapper modules (one per backend resource) that call fetch or axios against /api/* and return typed response objects.

Key Architectural Patterns

The backend uses gin-contrib/sessions with a cookie store. After a successful OAuth flow, userId is written into the encrypted session cookie. The AuthRequired() middleware reads this key on every authenticated request — there are no JWTs or API keys in the default setup. The session cookie is scoped to the domain, which means the frontend and backend must share an origin (or be proxied through the same Nginx host) for cookies to be sent correctly.
services/calendar/ defines a CalendarProvider interface with two methods: GetCalendarList() and GetCalendarEvents(calendarId, timeMin, timeMax). Google, Microsoft Graph, and Apple all implement this interface. When a user has multiple connected accounts, the backend fans out a goroutine per account using the GetCalendarEventsAsync and GetCalendarListAsync helpers, each writing results to a Go channel. The main goroutine collects from the channel as results arrive, achieving parallel I/O without a thread-per-request model.
The EventType constant in models/event.go drives how the availability grid is rendered:
  • specific_dates — the poll spans an explicit list of calendar dates (e.g., Mon 3 Jun – Fri 7 Jun).
  • dow — the poll spans recurring day-of-week columns (e.g., “every Mon / Wed / Fri”) without binding to specific dates; useful for finding a recurring meeting slot.
  • group — a live availability group that continuously reflects members’ real calendar data rather than a one-time poll response.
When the Go binary starts and ../frontend/dist is absent (standard in the Docker Compose setup), it logs "Frontend directory not found, skipping static file serving" and registers a JSON 404 handler for all non-API routes. The frontend is served entirely by Nginx in its own container. This separation means the backend image has no Node.js toolchain and the frontend image has no Go runtime, keeping image sizes small and attack surfaces minimal.

Next Steps

Docker Self-Hosting

Production Nginx reverse proxy configuration, SSL, custom domains, and CORS setup.

API Reference

Full endpoint documentation generated from the Swagger spec, with example requests and responses.

Security

Hardening checklist: encryption key rotation, container capabilities, HTTPS enforcement, and backup strategy.

Build docs developers (and LLMs) love