Skip to content

Database

FluxSend uses PostgreSQL as its primary database. The application manages its own schema via migrations on every startup — no manual migration steps are needed.


Requirements

Requirement Version
PostgreSQL 13+ (16 recommended for development)
pgcrypto extension Required (enabled automatically by migrations)

The pgcrypto extension is used for gen_random_uuid() (UUID primary keys) and crypt() (API key bcrypt comparison in queries).


Environment variables

Variable Description Default Required
DB_HOST PostgreSQL hostname — Yes
POSTGRES_USER Database user — Yes
POSTGRES_PASSWORD Database password — Yes
POSTGRES_DB Database name — Yes

Connection string

The connection string is built automatically at startup:

postgres://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{DB_HOST}:5432/{POSTGRES_DB}?sslmode=disable
  • Port: Hardcoded to 5432 (not configurable)
  • SSL mode: Hardcoded to disable (not configurable)

Dev defaults (from .env)

DB_HOST=localhost
POSTGRES_USER=devuser
POSTGRES_PASSWORD=devpass
POSTGRES_DB=devdb

Docker Compose (development)

The docker-compose.yaml provides a PostgreSQL 16 service:

dev-postgres:
  image: postgres:16
  container_name: dev-postgres
  environment:
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    - POSTGRES_DB=${POSTGRES_DB}
  ports:
    - "5432:5432"
  restart: unless-stopped

Start it:

make db

Stop it:

make db-down

Note: The dev PostgreSQL container has no persistent volume. Data is lost when the container is removed.


Migration system

Migrations run automatically on every startup — there is no manual migrate up step.

Library

github.com/golang-migrate/migrate/v4 v4.19.1 with the file source driver and postgres database driver.

Migration files

Located at internal/repo/migrations/. Each migration has an up file (.up.sql) and optionally a down file (.down.sql).

Migration Description
000001_init_schema Creates users, files, shares tables
000002_private_downloads Adds private_download_token to files
000003_delete_file_constraint Drops old uniqueness constraint
000004_new_file_constraint Adds files_unique constraint
000005_notes_table Creates notes table
000006_unique_user_file_note_constraint Adds unique constraint on notes
000007_unique_file_name_per_owner Deduplicates files, adds per-owner unique name
000008_received_seen_at Adds received_seen_at to shares
000009_idx_public_shares Creates unique index on public shares
000010_uuid_user_id Migrates user PK from google_id (TEXT) to UUID
000011_add_storage_mapping_uuid Adds storage_mapping UUID to files
000012_identities_table Creates identities and sessions tables, refactors users
000013_cleanup_old_constraints Drops and recreates foreign keys
000014_user_plans Creates plans and plan_features, seeds free/developer/enterprise plans
000015_files_created_at Adds created_at to files
000016_share_password_protected Adds password_hash to shares
000017_share_failed_attempts Adds failed_attempts to shares
000018_workspaces Creates workspaces, workspace_members, workspace_invites
000019_workspace_admin_role Adds admin role to workspace enums
000020_workspace_files Creates workspace_files table
000021_workspace_plans Adds workspace quota columns to plans
000022_api_keys Creates api_keys, api_key_scopes, api_key_user_assignments, api_key_workspaces
000023_api_key_plan_limits Adds API key limit columns to plans

Tables

After all migrations, the schema contains:

  • users — user accounts
  • files — file metadata
  • shares — sharing records (private, public, password-protected)
  • notes — file notes
  • identities — OAuth provider identities (Google, GitHub)
  • sessions — authentication sessions
  • plans — plan definitions with quota limits
  • plan_features — per-plan feature flags
  • workspaces — collaborative workspaces
  • workspace_members — workspace membership and roles
  • workspace_invites — pending workspace invitations
  • workspace_files — workspace file records with paths
  • api_keys — API keys with bcrypt hashes
  • api_key_scopes — permissions per API key
  • api_key_user_assignments — private key to user bindings
  • api_key_workspaces — workspace key to workspace bindings
  • users_old — legacy table, retained after UUID migration

Custom migration

In addition to SQL files, a Go-based migration BackfillPrivateDownloadTokens() runs on every startup. It:

  1. Finds files missing a private_download_token
  2. Generates and assigns one using pkg.GenerateSecureTokenFromID()
  3. Adds a NOT NULL constraint and UNIQUE index on the column if absent

Connection pool

The application uses Go's database/sql defaults (no custom pool configuration):

Setting Default Implication
MaxOpenConns 0 (unlimited) No cap on concurrent connections
MaxIdleConns 2 Only 2 idle connections kept
ConnMaxLifetime 0 (unlimited) Connections never recycled
ConnMaxIdleTime 0 (unlimited) Idle connections kept forever

Pool settings are not configurable via environment variables.


sqlc (code generation)

SQL queries are defined in internal/repo/query/ and compiled to type-safe Go code in internal/repo/sqlc/.

Configuration (sqlc.yaml):

version: "2"
sql:
  - engine: "postgresql"
    schema: "./internal/repo/migrations/"
    queries: "./internal/repo/query/"
    gen:
      go:
        package: "sqlc"
        emit_json_tags: true
        emit_interface: true
        out: "./internal/repo/sqlc"

Regenerate after changing queries or schema:

sqlc generate

Query files

File Queries
users.sql 7 — create, get by ID/email, get/update bucket, delete
files.sql 11 — insert, get by owner/name/id/token, delete, rename
shares.sql 16 — insert (public/private/password-protected), get by token, mark seen, delete, count unseen, increment failed attempts
notes.sql 2 — upsert note, get note
sessions.sql 3 — create, get (with expiry), delete
identities.sql 3 — get by provider, create, get by user
plans.sql 11 — get user+plan, quota checks, user stats, daily usage
private_downloads.sql 3 — list missing tokens, update, get by name
workspaces.sql 26 — CRUD, members, invites, quota checks
workspace_files.sql 14 — CRUD, folder management, move, uploader info
api_keys.sql 10 — create, assign (private/workspace), list, revoke, scope lookup, quota check, bcrypt auth lookup

Startup behavior

On every startup the application:

  1. Reads DB env vars — panics if DB_HOST, POSTGRES_USER, POSTGRES_PASSWORD, or POSTGRES_DB are empty
  2. Opens a connection with sql.Open("postgres", connString)
  3. Runs all pending migrations via golang-migrate (Up())
  4. Runs the BackfillPrivateDownloadTokens custom migration
  5. Proceeds with server startup

There is no retry logic — if the database is unreachable at startup, the application exits with a fatal error.