Database¶
Database engine¶
| Item | Value |
|---|---|
| Engine | PostgreSQL |
| Client | github.com/jackc/pgx/v5 pgxpool |
| ORM | None |
| Named schema | Not created; objects use the connection search path |
| SQLite/MySQL/Redis | Not supported |
DSN sources: CENTRAL_DATABASE_URL, then DATABASE_URL.
Connection pooling¶
pgxpool.New(ctx, databaseURL) followed by Ping. Central does not set explicit max/min connections, idle timeouts, or lifetimes; those use pgxpool defaults and any DSN/URL parameters you supply.
Startup uses a 10-second context for connect + EnsureSchema. Request handlers use r.Context() without additional DB deadlines. The pool is closed on process exit.
Database initialization¶
On startup when a DSN is present:
- Open and ping the pool
- Run
PostgresStore.EnsureSchema(unversioned SQL statements) - Wire auth provider / session service
EnsurePlatformServiceResourcesfor platform service ACL rows- Bootstrap a local admin if
dashboard_userscount is zero - During empty-admin bootstrap, delete expired sessions once
There is no versioned migration framework, migration table, or rollback set. deploy/sql/portal_cards.sql creates only portal_cards and is not a complete schema substitute.
Schema statements execute one-by-one (non-transactional). A mid-init failure can leave a partial schema; a restart may repair additive portions.
Schemas / tables¶
Central declares eight runtime tables.
ER overview¶
erDiagram
dashboard_users ||--o{ dashboard_sessions : username
dashboard_users ||--o{ personal_access_tokens : user_id
dashboard_users ||--o{ auth_group_members : user_id
auth_groups ||--o{ auth_group_members : group_id
auth_groups ||--o{ resource_permissions : group_id
resources ||--o{ resource_permissions : resource_id
dashboard_users ||--o{ resources : created_by
portal_cards }o..o{ resources : "service+resource_key logical"
portal_cards¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL PK |
|
title |
TEXT NOT NULL |
|
description |
TEXT |
nullable |
card_type |
TEXT NOT NULL |
Go-validated: map, dashboard, report, dataset, external, admin |
url |
TEXT NOT NULL |
absolute http(s) or root-relative |
icon |
TEXT |
|
image_url |
TEXT |
|
category |
TEXT |
|
sort_order |
INTEGER DEFAULT 0 |
|
is_public |
BOOLEAN DEFAULT FALSE |
used for manual cards |
source_service |
TEXT |
logical link to resources.service |
source_resource_id |
TEXT |
logical link to resources.resource_key |
is_imported |
BOOLEAN DEFAULT FALSE |
|
created_at |
TIMESTAMPTZ DEFAULT NOW() |
Indexes:
portal_cards_visible_idx (is_public, sort_order, title)portal_cards_category_idx (category)portal_cards_source_idx (source_service, source_resource_id)
There is no FK to resources and no uniqueness constraint on imported source pairs.
dashboard_users¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL UNIQUE NOT NULL |
also unique index dashboard_users_id_key |
username |
TEXT PK |
login key |
email |
TEXT NOT NULL DEFAULT '' |
not used as login identity |
password_hash |
TEXT NOT NULL |
PBKDF2-SHA256 encoded string |
groups |
JSONB NOT NULL DEFAULT '[]' |
legacy mirror of memberships |
is_admin |
BOOLEAN NOT NULL DEFAULT FALSE |
legacy/admin mirror |
enabled |
BOOLEAN NOT NULL DEFAULT TRUE |
|
created_at |
TIMESTAMPTZ DEFAULT NOW() |
dashboard_sessions¶
| Column | Type | Notes |
|---|---|---|
token_hash |
TEXT PK |
SHA-256 hex of cookie value |
username |
TEXT NOT NULL |
FK → dashboard_users(username) ON DELETE CASCADE |
expires_at |
TIMESTAMPTZ NOT NULL |
|
created_at |
TIMESTAMPTZ DEFAULT NOW() |
Index: idx_dashboard_sessions_expires_at (expires_at).
personal_access_tokens¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL PK |
|
user_id |
INTEGER NOT NULL |
FK → dashboard_users(id) ON DELETE CASCADE |
name |
TEXT NOT NULL |
|
token_hash |
TEXT NOT NULL UNIQUE |
SHA-256 hex of agp_... |
token_prefix |
TEXT NOT NULL |
first 12 chars for UI |
scopes |
JSONB NOT NULL DEFAULT '[]' |
currently empty on create |
expires_at |
TIMESTAMPTZ |
nullable |
last_used_at |
TIMESTAMPTZ |
throttled updates (~5 minutes) |
created_at |
TIMESTAMPTZ NOT NULL DEFAULT NOW() |
|
revoked_at |
TIMESTAMPTZ |
soft revoke |
Indexes: idx_personal_access_tokens_user_id, idx_personal_access_tokens_token_hash (in addition to UNIQUE).
auth_groups¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL PK |
|
name |
VARCHAR(100) UNIQUE NOT NULL |
case-sensitive uniqueness |
description |
TEXT |
|
created_at |
TIMESTAMP DEFAULT NOW() |
no time zone |
Seeded names: Administrators, Editors, Analysts, Customers, Public. Application code protects renaming/deleting exact-case Administrators and Public.
auth_group_members¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL PK |
|
group_id |
INTEGER |
FK → auth_groups(id) ON DELETE CASCADE (nullable at DDL level) |
user_id |
INTEGER |
FK → dashboard_users(id) ON DELETE CASCADE |
created_at |
TIMESTAMP DEFAULT NOW() |
Unique: (group_id, user_id).
resources¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL PK |
|
resource_type |
VARCHAR(100) NOT NULL |
e.g. service, dataset types from other apps |
resource_key |
VARCHAR(255) NOT NULL |
|
display_name |
VARCHAR(255) |
|
service |
VARCHAR(100) |
|
created_by |
INTEGER |
FK → dashboard_users(id) (no ON DELETE action) |
created_at |
TIMESTAMP DEFAULT NOW() |
Unique: (resource_type, resource_key).
Startup upserts platform service resources (resource_type=service, service=platform):
| Key | Display name |
|---|---|
qgis_publish |
QGIS Publish Service |
geovault |
GeoVault |
jasper_reports |
Jasper Reports |
dashboard_service |
Dashboard Service |
tileserver_admin |
TileServer Admin |
postgis_service |
PostGIS Service |
Newly inserted rows receive Administrators admin permission.
resource_permissions¶
| Column | Type | Notes |
|---|---|---|
id |
SERIAL PK |
|
resource_id |
INTEGER |
FK → resources(id) ON DELETE CASCADE |
group_id |
INTEGER |
FK → auth_groups(id) ON DELETE CASCADE |
permission |
VARCHAR(50) NOT NULL |
runtime: view, edit, admin |
created_at |
TIMESTAMP DEFAULT NOW() |
Unique: (resource_id, group_id, permission).
Permission implication in queries: admin satisfies all; edit satisfies view.
Relationships used by portal visibility¶
- Administrators see every card.
- Manual cards (
source_serviceorsource_resource_idempty) requireis_public=true. - Resource-backed cards require a matching
resourcesrow whereservice = source_serviceandresource_key = source_resource_id, plus aresource_permissionsgrant for one of the viewer’s groups (Public always included).
Note: resources are unique on (resource_type, resource_key), while card discovery joins on (service, resource_key).
Migrations¶
| Mechanism | Present? |
|---|---|
| Versioned migrations | No |
EnsureSchema on startup |
Yes |
Standalone SQL (deploy/sql/portal_cards.sql) |
Cards only |
| Rollback scripts | No |
Verified discrepancies¶
- Standalone SQL omits auth/ACL/session/PAT tables.
- Existing tables are not fully reconciled; only some ALTERs exist (notably for
portal_cardsand limiteddashboard_userscolumns). - Discovery may emit card types such as
tilelayer/pipelinethat create-card validation rejects. - Mixed
TIMESTAMPTZvsTIMESTAMPacross tables. - Redundant uniqueness/indexes on some columns (
dashboard_users.id, PATtoken_hash).
Backups¶
No backup/restore implementation exists in Central. Operators should back up:
- PostgreSQL database referenced by the DSN
- Thumbnail directory (
CENTRAL_THUMBNAILS_DIR) /etc/central.envand deployed binary/assets