postgres

package
v0.16.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

Documentation

Overview

Package postgres provides a Postgres-backed implementation of service.Repository for the identity service.

This is an alternative to the EntDB-backed repository. The two are interchangeable from the AuthService's point of view: both implement the same service.Repository interface.

Why Postgres

Postgres is well understood, ubiquitous in production environments, and ships with a mature ops toolkit (pg_dump, pg_basebackup, replicas, PITR via WAL archiving). It is the right default for teams that want to run identity without taking a dependency on the tenant-shard-db stack.

Driver choice

The implementation uses pgx/v5 directly (not via database/sql). pgxpool.Pool gives us a tuneable connection pool; pgx is also faster than database/sql and exposes Postgres-specific features (LISTEN / NOTIFY, COPY, JSONB native typing) that we may want later.

Migrations

Schema DDL lives under migrations/ and is applied via golang-migrate/migrate using the embed.FS source. By default New() does NOT run pending migrations on connect — production deploys run migrations out-of-band as a separate Job, so a rolling rollout never races two replicas to apply the same change. Set Config.AutoMigrate to true (or GATEWAY_POSTGRES_AUTO_MIGRATE=true) only for local dev and single-replica environments.

Multi-tenancy

Every table carries a tenant_id text not null column and uniqueness constraints are scoped to (tenant_id, ...). A pgRepository instance is constructed with a single tenant_id and writes/reads only that tenant's rows.

Error mapping

Postgres unique-violation errors (SQLSTATE 23505) are mapped to service.ErrAlreadyExists by errors.go::wrapPgErr. ErrNoRows is surfaced as a nil result (not an error), matching the existing in-memory and EntDB drivers.

Index

Constants

View Source
const DefaultConnTimeout = 5 * time.Second

DefaultConnTimeout is used when Config.ConnTimeout is zero.

View Source
const DefaultMaxConns int32 = 25

DefaultMaxConns is used when Config.MaxConns is zero.

Variables

This section is empty.

Functions

func Migrate added in v0.15.0

func Migrate(dsn string) error

Migrate applies all pending schema migrations to the Postgres database at dsn, then returns. It is idempotent — a fully-migrated database is a no-op — and safe to run concurrently with other instances: the underlying runner holds a Postgres advisory lock for the duration, so exactly one caller applies the migrations and the rest wait, then no-op. It is the entry point behind the `identity migrate` deploy step (the explicit alternative to GATEWAY_POSTGRES_AUTO_MIGRATE).

func New

func New(ctx context.Context, cfg Config) (*pgRepository, error)

New constructs a Postgres-backed repository:

  1. Parse / validate cfg.
  2. Open a pgxpool with cfg.MaxConns, cfg.ConnTimeout.
  3. Optionally run pending migrations (cfg.AutoMigrate=true).
  4. Ping to fail fast on a misconfigured DSN.

The returned store implements both service.Repository and service.DB. The caller is responsible for keeping the *pgRepository alive for the lifetime of the service; pool resources are released by Close().

Types

type Config

type Config struct {
	DSN         string
	MaxConns    int32
	ConnTimeout time.Duration
	AutoMigrate bool
	TenantID    string
}

Config controls how the postgres repository connects to its database.

DSN is the libpq-style connection string, e.g.

postgres://user:pass@host:5432/dbname?sslmode=disable

MaxConns caps the underlying pgxpool. A zero value means "use the pgxpool default" (currently 4 + GOMAXPROCS-ish). 25 is the suggested default for an identity service node (see DefaultMaxConns).

ConnTimeout is the per-acquire timeout used when checking a connection out of the pool. It does NOT bound the total query time — callers are still responsible for passing a context with the appropriate deadline.

AutoMigrate controls whether New() applies pending schema migrations on first connect. In CI / dev / test we want true (the default); in strict production deploys teams may flip it to false and run `migrate ... up` from a deploy pipeline instead.

TenantID is the tenant whose rows this repository instance writes and reads. Multi-tenant deployments construct one repository per tenant; the most common single-tenant config plumbs cfg.DefaultTenantID straight through.

func ConfigFromEnv

func ConfigFromEnv(tenantID string) Config

ConfigFromEnv reads Config values from GATEWAY_POSTGRES_* env vars. It is a convenience for callers that don't want to plumb each field through their own config struct. tenantID is passed in (rather than read from env) because identity already plumbs cfg.DefaultTenantID.

type Project added in v0.15.0

type Project struct {
	ID             string
	StorageScopeID string
	Name           string
	Status         string // active | suspended
	ConfigJSON     string // JSON object; "" is normalised to "{}".
	CreatedAtMs    int64
	UpdatedAtMs    int64
}

Project is a control-plane registry row: one logical, control-plane isolation entity (a Firebase-style project) mapped onto exactly one physical storage scope (shard) via StorageScopeID.

type ProjectAuthDomain added in v0.15.0

type ProjectAuthDomain struct {
	ID           string
	ProjectID    string
	Hostname     string
	IsPrimary    bool
	VerifiedAtMs int64
	CreatedAtMs  int64
}

ProjectAuthDomain is a per-project serving hostname. One host resolves to exactly one project (Hostname is globally unique, case-insensitive), so the Host header alone can resolve a project.

type ProjectCredential added in v0.15.0

type ProjectCredential struct {
	ID           string
	ProjectID    string
	Kind         string // publishable | secret | mtls
	PublicID     string
	SecretHash   string
	Status       string // active | revoked
	CreatedAtMs  int64
	LastUsedAtMs int64
	RevokedAtMs  int64
}

ProjectCredential is a lookup key used to resolve a project on a request, by its globally-unique PublicID.

type ProjectStore added in v0.15.0

type ProjectStore struct {
	// contains filtered or unexported fields
}

ProjectStore is the Postgres-backed, control-plane registry store. It is platform-global (not project/tenant-scoped) and shares its caller's connection pool.

func NewProjectStore added in v0.15.0

func NewProjectStore(r *pgRepository) *ProjectStore

NewProjectStore builds a control-plane store that shares the given repository's connection pool. The store must NOT be closed independently — closing the owning *pgRepository releases the pool for every derived store.

func (*ProjectStore) CreateProject added in v0.15.0

func (s *ProjectStore) CreateProject(ctx context.Context, p *Project) (string, error)

CreateProject inserts a project. StorageScopeID is required and globally unique; a duplicate surfaces service.ErrAlreadyExists. The id is caller-supplied (random hex when empty) so it is known without a RETURNING round-trip; the assigned id is written back to p.ID.

func (*ProjectStore) CreateProjectAuthDomain added in v0.15.0

func (s *ProjectStore) CreateProjectAuthDomain(ctx context.Context, d *ProjectAuthDomain) (string, error)

CreateProjectAuthDomain inserts a serving hostname for a project. Hostname is globally unique on lower(hostname) — a duplicate (in any case) surfaces service.ErrAlreadyExists. At most one is_primary domain is allowed per project (partial unique index); a second primary for the same project likewise surfaces service.ErrAlreadyExists. The id is caller-supplied (random hex when empty) and written back to d.ID.

func (*ProjectStore) CreateProjectCredential added in v0.15.0

func (s *ProjectStore) CreateProjectCredential(ctx context.Context, c *ProjectCredential) (string, error)

CreateProjectCredential inserts a lookup credential for a project. PublicID is required and globally unique; a duplicate surfaces service.ErrAlreadyExists. The id is caller-supplied (random hex when empty) and written back to c.ID.

func (*ProjectStore) EnsureDefaultProject added in v0.15.0

func (s *ProjectStore) EnsureDefaultProject(ctx context.Context, projectID, storageScopeID, name string) (*Project, error)

EnsureDefaultProject idempotently ensures the default Project exists, mapped onto the given storage scope (typically GATEWAY_DEFAULT_TENANT_ID). It is safe to call on every boot and from multiple instances at once: it returns the existing project when one is already present (looked up by id, then by storage scope), and otherwise creates it, tolerating a concurrent creator (an ErrAlreadyExists race is resolved by re-reading the row).

The default project is a logical control-plane entity that POINTS AT the storage scope via StorageScopeID — it is not the same value as the storage id, and the two must not be conflated.

If the storage scope is already mapped to a project (storage_scope_id is globally unique), that existing project is returned even when its id differs from projectID — the scope binding wins and no second project is created for the same scope.

func (*ProjectStore) GetProjectByAuthHostname added in v0.15.0

func (s *ProjectStore) GetProjectByAuthHostname(ctx context.Context, hostname string) (*Project, error)

GetProjectByAuthHostname resolves a project from a request's Host header. The hostname match is case-insensitive (lower(hostname)), matching the global unique index, so one host resolves to exactly one project. Returns (nil, nil) when no auth domain matches the host.

func (*ProjectStore) GetProjectByID added in v0.15.0

func (s *ProjectStore) GetProjectByID(ctx context.Context, projectID string) (*Project, error)

GetProjectByID returns the project with the given id, or (nil, nil) when no such project exists.

func (*ProjectStore) GetProjectByStorageScope added in v0.15.0

func (s *ProjectStore) GetProjectByStorageScope(ctx context.Context, storageScopeID string) (*Project, error)

GetProjectByStorageScope returns the single project mapped onto the given physical storage scope, or (nil, nil) when none is. storage_scope_id is globally unique, so this resolves at most one project.

func (*ProjectStore) GetProjectCredentialByPublicID added in v0.15.0

func (s *ProjectStore) GetProjectCredentialByPublicID(ctx context.Context, publicID string) (*ProjectCredential, error)

GetProjectCredentialByPublicID resolves a credential (and thus its owning project_id, kind and status) by its globally-unique public_id, or returns (nil, nil) when none matches. This is the key-based per-request project-resolution path.

func (*ProjectStore) ListProjectAuthDomains added in v0.15.0

func (s *ProjectStore) ListProjectAuthDomains(ctx context.Context, projectID string) ([]*ProjectAuthDomain, error)

ListProjectAuthDomains returns every auth domain for a project, ordered primary-first then by creation time, so callers can pick the link-building host deterministically. An unknown project yields an empty slice.

func (*ProjectStore) ResolveByCredential added in v0.16.0

func (s *ProjectStore) ResolveByCredential(ctx context.Context, publicID string) (*service.ResolvedProject, error)

ResolveByCredential resolves the active project an active credential public id belongs to. A revoked credential, a suspended project, an unknown id, or a blank id is a clean miss (nil, nil); only an infrastructure failure returns an error.

func (*ProjectStore) ResolveByHostname added in v0.16.0

func (s *ProjectStore) ResolveByHostname(ctx context.Context, hostname string) (*service.ResolvedProject, error)

ResolveByHostname resolves the active project a serving hostname maps onto (case-insensitive). An unmapped hostname, a suspended project, or a blank hostname is a clean miss (nil, nil).

func (*ProjectStore) RevokeProjectCredential added in v0.15.0

func (s *ProjectStore) RevokeProjectCredential(ctx context.Context, credentialID string, atMs int64) error

RevokeProjectCredential marks a credential revoked at atMs (defaulting to now when zero). Revoking is idempotent: an already-revoked or non-existent credential is a no-op that returns nil. A revoked credential no longer resolves a project for new requests, though the row is retained for audit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL