54 lines
3.6 KiB
Go
54 lines
3.6 KiB
Go
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
// Package cmssqlite stores cms values in an application's SQLite transaction.
|
|
// Schema installation is explicit. Callers own database opening, migration
|
|
// versions, authorization and audits. Every mutation must use a caller-owned
|
|
// transaction, committing its domain change and audit together; never use a
|
|
// pooled *sql.DB for multi-statement writes. Namespaces isolate application data.
|
|
package cmssqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"gamertan.com/web/cms"
|
|
)
|
|
|
|
type Queryer interface {
|
|
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
|
|
QueryRowContext(context.Context, string, ...any) *sql.Row
|
|
}
|
|
|
|
// Reader may use a database or read transaction. Writers below require *sql.Tx.
|
|
type Reader struct {
|
|
db Queryer
|
|
scope string
|
|
}
|
|
|
|
func New(db Queryer, scope string) (*Reader, error) {
|
|
if db == nil || !cms.ValidID(scope) {
|
|
return nil, cms.ErrInvalid
|
|
}
|
|
return &Reader{db: db, scope: scope}, nil
|
|
}
|
|
|
|
// CreateSchema must be called from the application's explicit migration.
|
|
// It never changes an existing publishing schema or starts a transaction.
|
|
func CreateSchema(ctx context.Context, tx *sql.Tx) error {
|
|
for _, statement := range []string{
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_taxonomies (scope TEXT NOT NULL,id TEXT NOT NULL,slug TEXT NOT NULL,name TEXT NOT NULL,description TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>0),active INTEGER NOT NULL CHECK(active IN (0,1)),PRIMARY KEY(scope,id),UNIQUE(scope,slug))`,
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_terms (scope TEXT NOT NULL,id TEXT NOT NULL,taxonomy_id TEXT NOT NULL,slug TEXT NOT NULL,name TEXT NOT NULL,description TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>0),active INTEGER NOT NULL CHECK(active IN (0,1)),PRIMARY KEY(scope,id),UNIQUE(scope,taxonomy_id,slug),FOREIGN KEY(scope,taxonomy_id) REFERENCES gwf_cms_taxonomies(scope,id))`,
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_term_slugs (scope TEXT NOT NULL,taxonomy_id TEXT NOT NULL,slug TEXT NOT NULL,term_id TEXT NOT NULL,PRIMARY KEY(scope,taxonomy_id,slug),FOREIGN KEY(scope,term_id) REFERENCES gwf_cms_terms(scope,id))`,
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_resources (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,published_revision INTEGER NOT NULL DEFAULT 0 CHECK(published_revision>=0),PRIMARY KEY(scope,kind,id))`,
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_associations (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,revision INTEGER NOT NULL CHECK(revision>0),document TEXT NOT NULL,PRIMARY KEY(scope,kind,id,revision),FOREIGN KEY(scope,kind,id) REFERENCES gwf_cms_resources(scope,kind,id))`,
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_memberships (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,revision INTEGER NOT NULL,term_id TEXT NOT NULL,PRIMARY KEY(scope,kind,id,revision,term_id),FOREIGN KEY(scope,kind,id,revision) REFERENCES gwf_cms_associations(scope,kind,id,revision),FOREIGN KEY(scope,term_id) REFERENCES gwf_cms_terms(scope,id))`,
|
|
`CREATE INDEX IF NOT EXISTS gwf_cms_memberships_term ON gwf_cms_memberships(scope,term_id,kind,id,revision)`,
|
|
`CREATE TABLE IF NOT EXISTS gwf_cms_links (scope TEXT NOT NULL,kind TEXT NOT NULL,id TEXT NOT NULL,revision INTEGER NOT NULL,target_kind TEXT NOT NULL,target_id TEXT NOT NULL,PRIMARY KEY(scope,kind,id,revision,target_kind,target_id),FOREIGN KEY(scope,kind,id,revision) REFERENCES gwf_cms_associations(scope,kind,id,revision))`,
|
|
`CREATE INDEX IF NOT EXISTS gwf_cms_links_target ON gwf_cms_links(scope,target_kind,target_id,kind,id,revision)`,
|
|
} {
|
|
if _, err := tx.ExecContext(ctx, statement); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|