Wednesday Waffle #
A lightweight webapp for hosting weekly short video check-ins between friends.
Prerequisites #
# macOS
brew install go ffmpeg
Running #
go run ./cmd/server
Server starts on http://localhost:8080.
Environment variables:
WAFFLE_ADDR— bind address (default:8080)WAFFLE_DB— SQLite path (default./waffle.db)WAFFLE_VIDEOS_DIR— uploaded video storage (default./videos)WAFFLE_PUBLIC_URL— public URL for invite linksWAFFLE_SECURE_COOKIES— settruewhen served over HTTPSWAFFLE_SESSION_MAX_AGE— session cookie lifetime in seconds (default 30 days)WAFFLE_TRANSCODE_WORKERS— max concurrent FFmpeg jobs (default2)WAFFLE_TRANSCODE_QUEUE— max queued transcode jobs (default10)WAFFLE_UPLOAD_RATE_LIMIT— max uploads per user per window (default10)WAFFLE_UPLOAD_RATE_WINDOW— rate-limit window in seconds (default3600)WAFFLE_UPLOAD_QUOTA_BYTES— per-user total storage quota in bytes (default1073741824, 1 GB)WAFFLE_BACKUP_DIR— directory for timestamped backups (empty disables backups)WAFFLE_BACKUP_RETENTION— number of backups to keep (default7)
Backups #
Run a one-off backup:
go run ./cmd/backup
The command copies the SQLite database (via VACUUM INTO) and the videos directory into a timestamped folder under WAFFLE_BACKUP_DIR. Older backups are pruned to WAFFLE_BACKUP_RETENTION copies.
For production, schedule this command daily with cron or a systemd timer.
Testing #
go test ./...
API Reference #
Join a conversation #
Creates a session for the user if the invite code is valid.
POST /api/conversations/join
Content-Type: application/json
{ "invite_code": "happy-blue-waffle", "username": "alice" }
Response sets a waffle_session cookie used for all subsequent requests.
Create a conversation #
Requires authentication. The creator is automatically added as a member.
POST /api/conversations
Content-Type: application/json
{ "name": "College Friends" }
Response:
{ "id": "...", "invite_code": "happy-blue-waffle", "name": "College Friends" }
List your conversations #
GET /api/conversations
Log out #
POST /api/logout
Clears the waffle_session cookie and invalidates the session on the server.
Upload a video #
POST /api/upload
Content-Type: multipart/form-data
fields:
- file (video file, supported: .mp4 .mov .avi .mkv)
- conversation_id (string)
Upload is accepted immediately (HTTP 202). Transcoding to 720p MP4 happens in the background with up to 3 retries. Original file is deleted only after successful transcoding.
List conversation members #
GET /api/conversations/<id>/members
Response:
["alice", "bob"]
Delete a video #
DELETE /api/videos/<id>
Only the uploader can delete their own video.
List videos in a conversation #
GET /api/videos?conversation_id=<id>
Response:
[
{
"id": "...",
"uploader": "alice",
"status": "ready",
"uploaded_at": "2026-02-20T12:00:00Z"
}
]
Video statuses: pending, ready, error.