diff options
| author | grm <grm@eyesin.space> | 2026-09-14 00:51:19 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-14 00:51:19 +0300 |
| commit | 282d2ab3e0fb3a74f32160a231225773001acd14 (patch) | |
| tree | b15c105c0c5d79467d3b815af4c67f5bf4a02d9d | |
| parent | 0de17af2318134549fa6f4db822ba6921b35f12f (diff) | |
| download | blogspace-282d2ab3e0fb3a74f32160a231225773001acd14.tar.gz blogspace-282d2ab3e0fb3a74f32160a231225773001acd14.tar.bz2 blogspace-282d2ab3e0fb3a74f32160a231225773001acd14.zip | |
Drop the single-database split and re-baseline the control migrations
Every deployment has been through the per-blog split, so the code that
performed it (split.go, control migrations 00002–00007) is dead weight, and a
fresh install replaying six migrations only to drop the tables again was
silly. The control chain is now a single 00001_init.sql with the final users
and blogs shape, matching the blog chain.
Goose ignores versions recorded in the database that no longer exist in the
source, but refuses a future migration numbered below the database's highest
version. Databases that went through the split therefore need
`DELETE FROM goose_db_version WHERE version_id > 1` once; README and
AGENTS.md say so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
| -rw-r--r-- | AGENTS.md | 11 | ||||
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | internal/db/db.go | 18 | ||||
| -rw-r--r-- | internal/db/migrations/control/00001_init.sql | 55 | ||||
| -rw-r--r-- | internal/db/migrations/control/00002_sections.sql | 19 | ||||
| -rw-r--r-- | internal/db/migrations/control/00003_layout.sql | 56 | ||||
| -rw-r--r-- | internal/db/migrations/control/00004_section_placement.sql | 13 | ||||
| -rw-r--r-- | internal/db/migrations/control/00005_per_blog_databases.sql | 12 | ||||
| -rw-r--r-- | internal/db/migrations/control/00007_drop_content.sql | 9 | ||||
| -rw-r--r-- | internal/db/split.go | 152 | ||||
| -rw-r--r-- | internal/store/modules.go | 2 |
11 files changed, 27 insertions, 329 deletions
@@ -47,8 +47,7 @@ cmd/blogspace/ main.go (serve|seed|migrate, superadmin + root blog bootst migrates every blog database at start), seed.go internal/config/ env → Config; RootSubdomain = "www" internal/db/ Cluster (control pool + lazy per-blog pools, CREATE/DROP DATABASE), - goose providers; migrations/control/*.sql, migrations/blog/*.sql, - split.go (control Go migration 00006: the one-off move to per-blog DBs) + goose providers; migrations/control/*.sql, migrations/blog/*.sql internal/store/ Store = control DB (users, blog registry, create/delete blog); BlogStore = one blog's DB (settings, pages, posts, images, sections, modules, menu) internal/auth/ bcrypt, JWT issue/parse, cookie, HMAC CSRF @@ -218,9 +217,11 @@ superadmin password to `admin`. Production refuses both. (runs in every blog database — it must not reference `users`/`blogs`) and `internal/db/migrations/control/` for users and the registry; goose `-- +goose Up/Down` sections; they run automatically at startup. Never - edit an applied migration. Control 00005–00007 are the one-off split (add - `db_name`, move content in `split.go`, drop the old tables) — a fresh - install still replays them. + edit an applied migration. Both chains were re-baselined at 00001 after + the move to per-blog databases; deployments from before it have + `goose_db_version` rows 2–7 in the control DB that must be deleted once + (README "Upgrading from a single database") or the next control migration + is refused as "missing". - Blog content goes through `BlogStore` methods, which take `ctx` first and are bound to one blog's database, so a blogger can never touch another blog's rows; users and the registry go through `Store`. @@ -126,7 +126,14 @@ above. A dump taken with an older version of Blogspace is upgraded at the next start (or with `blogspace migrate`). Deleting a user in `/admin/` drops their blog database — take a dump first if -you may want it back. Blog pools are small (4 connections each, closed when +you may want it back. + +Upgrading from a single database (installs older than the per-blog split): run +the release that contains the split once (it moves each blog into its own +database at start-up), then clear the old migration history so later +migrations apply: `docker compose exec db psql -U blogspace -c "DELETE FROM +goose_db_version WHERE version_id > 1"`. Current releases no longer carry the +split code. Blog pools are small (4 connections each, closed when idle); with many blogs busy at once, raise `max_connections` on the `db` service. ## Layout diff --git a/internal/db/db.go b/internal/db/db.go index 601680d..0f66fcc 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -139,12 +139,7 @@ func (c *Cluster) CreateBlogDB(ctx context.Context, dbName string) error { return nil } -// execer is the pool or connection createDatabase runs on. -type execer interface { - Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) -} - -func createDatabase(ctx context.Context, db execer, dbName string) error { +func createDatabase(ctx context.Context, db *pgxpool.Pool, dbName string) error { _, err := db.Exec(ctx, `CREATE DATABASE `+pgx.Identifier{dbName}.Sanitize()) var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "42P04" { // duplicate_database @@ -169,10 +164,9 @@ func (c *Cluster) DropBlogDB(ctx context.Context, dbName string) error { // ---- migrations ------------------------------------------------------------ -// MigrateControl applies the control database migrations, including the Go -// migration that moves content out into the blog databases. +// MigrateControl applies the control database migrations. func MigrateControl(ctx context.Context, controlURL string) error { - return migrate(ctx, controlURL, "migrations/control", goose.WithGoMigrations(splitMigration(controlURL))) + return migrate(ctx, controlURL, "migrations/control") } // MigrateBlog applies the blog schema migrations to one blog database. @@ -181,8 +175,8 @@ func MigrateBlog(ctx context.Context, blogURL string) error { } // migrate runs goose over database/sql. A Provider (rather than the package -// globals) keeps the two migration sets, and the Go migration, apart. -func migrate(ctx context.Context, dsn, dir string, opts ...goose.ProviderOption) error { +// globals) keeps the two migration sets apart. +func migrate(ctx context.Context, dsn, dir string) error { sqldb, err := sql.Open("pgx", dsn) if err != nil { return err @@ -192,7 +186,7 @@ func migrate(ctx context.Context, dsn, dir string, opts ...goose.ProviderOption) if err != nil { return err } - p, err := goose.NewProvider(goose.DialectPostgres, sqldb, fsys, opts...) + p, err := goose.NewProvider(goose.DialectPostgres, sqldb, fsys) if err != nil { return err } diff --git a/internal/db/migrations/control/00001_init.sql b/internal/db/migrations/control/00001_init.sql index e27dad4..0b26b37 100644 --- a/internal/db/migrations/control/00001_init.sql +++ b/internal/db/migrations/control/00001_init.sql @@ -1,4 +1,6 @@ -- +goose Up +-- The control database: accounts and the list of blogs. Everything a blog +-- contains lives in its own database (see migrations/blog). CREATE TABLE users ( id bigserial PRIMARY KEY, username text NOT NULL UNIQUE, @@ -9,60 +11,15 @@ CREATE TABLE users ( created_at timestamptz NOT NULL DEFAULT now() ); +-- "blog_" + subdomain must fit a 63-char Postgres identifier, hence 58. CREATE TABLE blogs ( id bigserial PRIMARY KEY, owner_id bigint NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE, - subdomain text NOT NULL UNIQUE CHECK (subdomain ~ '^[a-z0-9](-?[a-z0-9]){0,62}$'), - title text NOT NULL, - tagline text NOT NULL DEFAULT '', - theme jsonb NOT NULL DEFAULT '{}'::jsonb, - created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now() + subdomain text NOT NULL UNIQUE CHECK (subdomain ~ '^[a-z0-9](-?[a-z0-9]){0,57}$'), + db_name text NOT NULL UNIQUE, + created_at timestamptz NOT NULL DEFAULT now() ); -CREATE TABLE pages ( - id bigserial PRIMARY KEY, - blog_id bigint NOT NULL REFERENCES blogs(id) ON DELETE CASCADE, - slug text NOT NULL, - title text NOT NULL, - intro_md text NOT NULL DEFAULT '', - intro_html text NOT NULL DEFAULT '', - nav_order integer NOT NULL DEFAULT 0, - show_in_nav boolean NOT NULL DEFAULT true, - is_home boolean NOT NULL DEFAULT false, - created_at timestamptz NOT NULL DEFAULT now(), - UNIQUE (blog_id, slug) -); -CREATE UNIQUE INDEX pages_one_home_per_blog ON pages (blog_id) WHERE is_home; - -CREATE TABLE posts ( - id bigserial PRIMARY KEY, - page_id bigint NOT NULL REFERENCES pages(id) ON DELETE CASCADE, - slug text NOT NULL, - title text NOT NULL, - body_md text NOT NULL DEFAULT '', - body_html text NOT NULL DEFAULT '', - published boolean NOT NULL DEFAULT true, - created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now(), - UNIQUE (page_id, slug) -); -CREATE INDEX posts_page_created ON posts (page_id, created_at DESC); - -CREATE TABLE images ( - id uuid PRIMARY KEY, - blog_id bigint NOT NULL REFERENCES blogs(id) ON DELETE CASCADE, - filename text NOT NULL, - content_type text NOT NULL, - size integer NOT NULL, - data bytea NOT NULL, - created_at timestamptz NOT NULL DEFAULT now() -); -CREATE INDEX images_blog ON images (blog_id, created_at DESC); - -- +goose Down -DROP TABLE images; -DROP TABLE posts; -DROP TABLE pages; DROP TABLE blogs; DROP TABLE users; diff --git a/internal/db/migrations/control/00002_sections.sql b/internal/db/migrations/control/00002_sections.sql deleted file mode 100644 index 388cb17..0000000 --- a/internal/db/migrations/control/00002_sections.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +goose Up --- Announcements: blog-wide notices shown on every page and post. -CREATE TABLE sections ( - id bigserial PRIMARY KEY, - blog_id bigint NOT NULL REFERENCES blogs(id) ON DELETE CASCADE, - title text NOT NULL DEFAULT '', - body_md text NOT NULL DEFAULT '', - body_html text NOT NULL DEFAULT '', - placement text NOT NULL DEFAULT 'above' CHECK (placement IN ('above', 'below', 'sidebar')), - style text NOT NULL DEFAULT 'note' CHECK (style IN ('plain', 'note', 'warning')), - enabled boolean NOT NULL DEFAULT true, - sort_order integer NOT NULL DEFAULT 0, - created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now() -); -CREATE INDEX sections_blog ON sections (blog_id, sort_order, id); - --- +goose Down -DROP TABLE sections; diff --git a/internal/db/migrations/control/00003_layout.sql b/internal/db/migrations/control/00003_layout.sql deleted file mode 100644 index e8fff23..0000000 --- a/internal/db/migrations/control/00003_layout.sql +++ /dev/null @@ -1,56 +0,0 @@ --- +goose Up --- Layout modules: what each area of a blog (header, columns, footer) shows. -CREATE TABLE modules ( - id bigserial PRIMARY KEY, - blog_id bigint NOT NULL REFERENCES blogs(id) ON DELETE CASCADE, - area text NOT NULL CHECK (area IN ('header', 'left', 'right', 'above', 'below', 'footer')), - kind text NOT NULL CHECK (kind IN ('title', 'logo', 'menu', 'archive', 'recent', 'html', 'rss', 'text', 'sitemap')), - title text NOT NULL DEFAULT '', - body text NOT NULL DEFAULT '', - count integer NOT NULL DEFAULT 5, - sort_order integer NOT NULL DEFAULT 0, - created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now() -); -CREATE INDEX modules_blog ON modules (blog_id, area, sort_order, id); - --- The menu: blog pages and custom links in one ordered list. -CREATE TABLE menu_items ( - id bigserial PRIMARY KEY, - blog_id bigint NOT NULL REFERENCES blogs(id) ON DELETE CASCADE, - page_id bigint REFERENCES pages(id) ON DELETE CASCADE, - label text NOT NULL DEFAULT '', - url text NOT NULL DEFAULT '', - sort_order integer NOT NULL DEFAULT 0, - CHECK (page_id IS NOT NULL OR url <> '') -); -CREATE UNIQUE INDEX menu_items_page ON menu_items (page_id) WHERE page_id IS NOT NULL; -CREATE INDEX menu_items_blog ON menu_items (blog_id, sort_order, id); - --- Existing blogs keep the menu they had: pages marked "show in menu", in menu --- order, with the home page only if the theme included it. -INSERT INTO menu_items (blog_id, page_id, sort_order) -SELECT p.blog_id, p.id, row_number() OVER (PARTITION BY p.blog_id ORDER BY p.nav_order, p.id) - 1 -FROM pages p JOIN blogs b ON b.id = p.blog_id -WHERE p.show_in_nav AND (NOT p.is_home OR coalesce((b.theme->>'nav_show_home')::boolean, true)); -ALTER TABLE pages DROP COLUMN show_in_nav; - --- ...and the layout their theme described. -INSERT INTO modules (blog_id, area, kind, sort_order) -SELECT id, 'header', 'title', CASE WHEN theme->>'nav_position' = 'top-bar' THEN 1 ELSE 0 END -FROM blogs WHERE coalesce((theme->>'header_show_title')::boolean, true); -INSERT INTO modules (blog_id, area, kind, sort_order) -SELECT id, 'header', 'menu', CASE WHEN theme->>'nav_position' = 'top-bar' THEN 0 ELSE 1 END -FROM blogs WHERE coalesce(theme->>'nav_position', 'below-header') <> 'left-sidebar'; -INSERT INTO modules (blog_id, area, kind, sort_order) -SELECT id, 'left', 'menu', 0 FROM blogs WHERE theme->>'nav_position' = 'left-sidebar'; -INSERT INTO modules (blog_id, area, kind, body, sort_order) -SELECT id, 'footer', 'text', theme->>'footer_text', 0 FROM blogs WHERE coalesce(theme->>'footer_text', '') <> ''; -INSERT INTO modules (blog_id, area, kind, sort_order) -SELECT id, 'footer', 'rss', 1 FROM blogs WHERE coalesce((theme->>'footer_show_rss')::boolean, true); - --- +goose Down -ALTER TABLE pages ADD COLUMN show_in_nav boolean NOT NULL DEFAULT true; -UPDATE pages SET show_in_nav = EXISTS (SELECT 1 FROM menu_items m WHERE m.page_id = pages.id); -DROP TABLE menu_items; -DROP TABLE modules; diff --git a/internal/db/migrations/control/00004_section_placement.sql b/internal/db/migrations/control/00004_section_placement.sql deleted file mode 100644 index c36787e..0000000 --- a/internal/db/migrations/control/00004_section_placement.sql +++ /dev/null @@ -1,13 +0,0 @@ --- +goose Up --- Announcements are placed in a column (left, main, right) at its top or bottom. -ALTER TABLE sections DROP CONSTRAINT sections_placement_check; -UPDATE sections SET placement = CASE placement WHEN 'above' THEN 'main-top' WHEN 'below' THEN 'main-bottom' ELSE 'left-top' END; -ALTER TABLE sections ALTER COLUMN placement SET DEFAULT 'main-top'; -ALTER TABLE sections ADD CONSTRAINT sections_placement_check - CHECK (placement IN ('left-top', 'left-bottom', 'main-top', 'main-bottom', 'right-top', 'right-bottom')); - --- +goose Down -ALTER TABLE sections DROP CONSTRAINT sections_placement_check; -UPDATE sections SET placement = CASE placement WHEN 'main-top' THEN 'above' WHEN 'main-bottom' THEN 'below' ELSE 'sidebar' END; -ALTER TABLE sections ALTER COLUMN placement SET DEFAULT 'above'; -ALTER TABLE sections ADD CONSTRAINT sections_placement_check CHECK (placement IN ('above', 'below', 'sidebar')); diff --git a/internal/db/migrations/control/00005_per_blog_databases.sql b/internal/db/migrations/control/00005_per_blog_databases.sql deleted file mode 100644 index 92d3fa2..0000000 --- a/internal/db/migrations/control/00005_per_blog_databases.sql +++ /dev/null @@ -1,12 +0,0 @@ --- +goose Up --- Each blog gets its own database; the registry remembers which one. --- db_name stays nullable until the Go migration 00006 has moved the content. -ALTER TABLE blogs ADD COLUMN db_name text UNIQUE; --- "blog_" + subdomain must fit in a 63-char Postgres identifier. -ALTER TABLE blogs DROP CONSTRAINT blogs_subdomain_check; -ALTER TABLE blogs ADD CONSTRAINT blogs_subdomain_check CHECK (subdomain ~ '^[a-z0-9](-?[a-z0-9]){0,57}$'); - --- +goose Down -ALTER TABLE blogs DROP CONSTRAINT blogs_subdomain_check; -ALTER TABLE blogs ADD CONSTRAINT blogs_subdomain_check CHECK (subdomain ~ '^[a-z0-9](-?[a-z0-9]){0,62}$'); -ALTER TABLE blogs DROP COLUMN db_name; diff --git a/internal/db/migrations/control/00007_drop_content.sql b/internal/db/migrations/control/00007_drop_content.sql deleted file mode 100644 index 9daa1ac..0000000 --- a/internal/db/migrations/control/00007_drop_content.sql +++ /dev/null @@ -1,9 +0,0 @@ --- +goose Up --- The content now lives in the per-blog databases (see 00006 in split.go); --- the control database keeps only users and the blog registry. -DROP TABLE menu_items, modules, sections, images, posts, pages; -ALTER TABLE blogs DROP COLUMN title, DROP COLUMN tagline, DROP COLUMN theme, DROP COLUMN updated_at; -ALTER TABLE blogs ALTER COLUMN db_name SET NOT NULL; - --- +goose Down --- Not reversible: the content is gone from this database. Restore from a backup instead. diff --git a/internal/db/split.go b/internal/db/split.go deleted file mode 100644 index 668d255..0000000 --- a/internal/db/split.go +++ /dev/null @@ -1,152 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - "fmt" - "log" - - "github.com/jackc/pgx/v5" - "github.com/pressly/goose/v3" -) - -// splitMigration is control migration 00006: it moves every blog's content out -// of the control database into a database of its own. It runs in the control -// transaction, so either every blog is moved and marked with its db_name or -// nothing changes; the blog databases it created are then dropped by hand -// (the error says which) and the next start retries. 00007 drops the old -// tables afterwards. -func splitMigration(controlURL string) *goose.Migration { - up := func(ctx context.Context, tx *sql.Tx) error { - return splitBlogs(ctx, tx, controlURL) - } - down := func(ctx context.Context, tx *sql.Tx) error { - return errors.New("the per-blog split cannot be undone; restore the control database from a backup") - } - return goose.NewGoMigration(6, &goose.GoFunc{RunTx: up}, &goose.GoFunc{RunTx: down}) -} - -// splitTable is one content table to copy: the query selects the blog's rows -// in the column order of the new table. -type splitTable struct { - name string - cols []string - query string // $1 = blog id - seq bool // bigserial id to bump after the copy -} - -var splitTables = []splitTable{ - {"pages", []string{"id", "slug", "title", "intro_md", "intro_html", "nav_order", "is_home", "created_at"}, - `SELECT id, slug, title, intro_md, intro_html, nav_order, is_home, created_at FROM pages WHERE blog_id=$1`, true}, - {"posts", []string{"id", "page_id", "slug", "title", "body_md", "body_html", "published", "created_at", "updated_at"}, - `SELECT p.id, p.page_id, p.slug, p.title, p.body_md, p.body_html, p.published, p.created_at, p.updated_at - FROM posts p JOIN pages g ON g.id=p.page_id WHERE g.blog_id=$1`, true}, - {"images", []string{"id", "filename", "content_type", "size", "data", "created_at"}, - `SELECT id, filename, content_type, size, data, created_at FROM images WHERE blog_id=$1`, false}, - {"sections", []string{"id", "title", "body_md", "body_html", "placement", "style", "enabled", "sort_order", "created_at", "updated_at"}, - `SELECT id, title, body_md, body_html, placement, style, enabled, sort_order, created_at, updated_at FROM sections WHERE blog_id=$1`, true}, - {"modules", []string{"id", "area", "kind", "title", "body", "count", "sort_order", "created_at", "updated_at"}, - `SELECT id, area, kind, title, body, count, sort_order, created_at, updated_at FROM modules WHERE blog_id=$1`, true}, - {"menu_items", []string{"id", "page_id", "label", "url", "sort_order"}, - `SELECT id, page_id, label, url, sort_order FROM menu_items WHERE blog_id=$1`, true}, -} - -func splitBlogs(ctx context.Context, tx *sql.Tx, controlURL string) error { - type blog struct { - id int64 - sub, title, tagline string - theme []byte - createdAt, updatedAt any - } - rows, err := tx.QueryContext(ctx, `SELECT id, subdomain, title, tagline, theme, created_at, updated_at FROM blogs WHERE db_name IS NULL ORDER BY id`) - if err != nil { - return err - } - var blogs []blog - for rows.Next() { - var b blog - if err := rows.Scan(&b.id, &b.sub, &b.title, &b.tagline, &b.theme, &b.createdAt, &b.updatedAt); err != nil { - rows.Close() - return err - } - blogs = append(blogs, b) - } - rows.Close() - if err := rows.Err(); err != nil { - return err - } - if len(blogs) == 0 { - return nil - } - - // DDL needs its own connection: CREATE DATABASE refuses to run in a transaction. - admin, err := pgx.Connect(ctx, controlURL) - if err != nil { - return err - } - defer admin.Close(ctx) - var created []string - for _, b := range blogs { - name := DBName(b.sub) - log.Printf("moving blog %q into database %s", b.sub, name) - if err := createDatabase(ctx, admin, name); err != nil { - return fmt.Errorf("%w — a leftover of an earlier failed split must be dropped by hand (created so far: %v)", err, created) - } - created = append(created, name) - if err := MigrateBlog(ctx, withDatabase(controlURL, name)); err != nil { - return fmt.Errorf("migrate %s: %w (drop the databases %v before retrying)", name, err, created) - } - if err := copyBlog(ctx, tx, withDatabase(controlURL, name), b.id, b.title, b.tagline, b.theme, b.createdAt, b.updatedAt); err != nil { - return fmt.Errorf("copy blog %q: %w (drop the databases %v before retrying)", b.sub, err, created) - } - if _, err := tx.ExecContext(ctx, `UPDATE blogs SET db_name=$1 WHERE id=$2`, name, b.id); err != nil { - return err - } - } - return nil -} - -// copyBlog streams one blog's rows from the control transaction into its new database. -func copyBlog(ctx context.Context, tx *sql.Tx, blogURL string, blogID int64, title, tagline string, theme []byte, createdAt, updatedAt any) error { - conn, err := pgx.Connect(ctx, blogURL) - if err != nil { - return err - } - defer conn.Close(ctx) - if _, err := conn.Exec(ctx, `INSERT INTO settings (title, tagline, theme, created_at, updated_at) VALUES ($1,$2,$3,$4,$5)`, - title, tagline, theme, createdAt, updatedAt); err != nil { - return fmt.Errorf("settings: %w", err) - } - for _, t := range splitTables { - rows, err := tx.QueryContext(ctx, t.query, blogID) - if err != nil { - return fmt.Errorf("%s: %w", t.name, err) - } - n := len(t.cols) - src := pgx.CopyFromFunc(func() ([]any, error) { - if !rows.Next() { - return nil, rows.Err() - } - vals := make([]any, n) - ptrs := make([]any, n) - for i := range vals { - ptrs[i] = &vals[i] - } - // database/sql hands back int64/string/bool/[]byte/time.Time, - // which pgx encodes for the matching column types. - return vals, rows.Scan(ptrs...) - }) - _, err = conn.CopyFrom(ctx, pgx.Identifier{t.name}, t.cols, src) - rows.Close() - if err != nil { - return fmt.Errorf("%s: %w", t.name, err) - } - if t.seq { - if _, err := conn.Exec(ctx, `SELECT setval(pg_get_serial_sequence($1,'id'), coalesce(max(id),0)+1, false) FROM `+pgx.Identifier{t.name}.Sanitize(), t.name); err != nil { - return fmt.Errorf("%s sequence: %w", t.name, err) - } - } - } - return nil -} diff --git a/internal/store/modules.go b/internal/store/modules.go index e0c3cd5..d077975 100644 --- a/internal/store/modules.go +++ b/internal/store/modules.go @@ -127,7 +127,7 @@ func (bs *BlogStore) ResetModules(ctx context.Context) error { } // insertDefaultModules is the starting layout: title and menu in the header, -// an RSS link in the footer. Must match what the layout migration gave old blogs. +// an RSS link in the footer. func insertDefaultModules(ctx context.Context, tx querier) error { _, err := tx.Exec(ctx, `INSERT INTO modules (area, kind, sort_order) VALUES ('header','title',0), ('header','menu',1), ('footer','rss',0)`) |
