diff --git a/backend/migrations/20260414000001_initial_schema.go b/backend/migrations/20260414000001_initial_schema.go index 1b6f99d..f27bc58 100644 --- a/backend/migrations/20260414000001_initial_schema.go +++ b/backend/migrations/20260414000001_initial_schema.go @@ -4,9 +4,10 @@ // scripts/pb-init-v3.sh. Also creates the initial superuser from the // POCKETBASE_ADMIN_EMAIL / POCKETBASE_ADMIN_PASSWORD env vars (first run only). // -// Existing installs: run `migrate history-sync` once after deploying the custom -// PocketBase binary to mark this migration as already applied, then let -// migration 2 add the three fields that were missing from the old script. +// This migration is intentionally idempotent: each collection is skipped if it +// already exists. This makes it safe to apply on an existing install without +// running `migrate history-sync` first — existing collections are left untouched +// and migration 2 still runs to add the three fields that were missing. package migrations import ( @@ -71,6 +72,18 @@ func init() { }) } +// ── Helpers ─────────────────────────────────────────────────────────────────── + +// saveIfAbsent saves the collection only when no collection with that name +// exists yet. This makes the migration safe to run on an existing install +// without history-sync — already-created collections are simply skipped. +func saveIfAbsent(app core.App, c *core.Collection) error { + if _, err := app.FindCollectionByNameOrId(c.Name); err == nil { + return nil // already exists — skip + } + return saveIfAbsent(app, c) +} + // ── Collection creators ─────────────────────────────────────────────────────── func createBooks(app core.App) error { @@ -89,7 +102,7 @@ func createBooks(app core.App) error { &core.TextField{Name: "meta_updated"}, &core.BoolField{Name: "archived"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createChaptersIdx(app core.App) error { @@ -103,7 +116,7 @@ func createChaptersIdx(app core.App) error { c.AddIndex("idx_chapters_idx_slug_number", true, "slug, number", "") // Allow fast "recently updated books" queries. c.AddIndex("idx_chapters_idx_created", false, "created", "") - return app.Save(c) + return saveIfAbsent(app, c) } func createRanking(app core.App) error { @@ -118,7 +131,7 @@ func createRanking(app core.App) error { &core.JSONField{Name: "genres"}, &core.TextField{Name: "source_url"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createProgress(app core.App) error { @@ -130,7 +143,7 @@ func createProgress(app core.App) error { &core.TextField{Name: "user_id"}, &core.NumberField{Name: "audio_time"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createScrapingTasks(app core.App) error { @@ -151,7 +164,7 @@ func createScrapingTasks(app core.App) error { &core.DateField{Name: "finished"}, &core.DateField{Name: "heartbeat_at"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createAudioJobs(app core.App) error { @@ -168,7 +181,7 @@ func createAudioJobs(app core.App) error { &core.DateField{Name: "finished"}, &core.DateField{Name: "heartbeat_at"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createAppUsers(app core.App) error { @@ -188,7 +201,7 @@ func createAppUsers(app core.App) error { &core.TextField{Name: "polar_subscription_id"}, &core.BoolField{Name: "notify_new_chapters"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createUserSessions(app core.App) error { @@ -203,7 +216,7 @@ func createUserSessions(app core.App) error { &core.TextField{Name: "created_at"}, &core.TextField{Name: "last_seen"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createUserLibrary(app core.App) error { @@ -215,7 +228,7 @@ func createUserLibrary(app core.App) error { &core.TextField{Name: "saved_at"}, &core.TextField{Name: "shelf"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createUserSettings(app core.App) error { @@ -233,7 +246,7 @@ func createUserSettings(app core.App) error { &core.BoolField{Name: "announce_chapter"}, &core.TextField{Name: "audio_mode"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createUserSubscriptions(app core.App) error { @@ -242,7 +255,7 @@ func createUserSubscriptions(app core.App) error { &core.TextField{Name: "follower_id", Required: true}, &core.TextField{Name: "followee_id", Required: true}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createBookComments(app core.App) error { @@ -256,7 +269,7 @@ func createBookComments(app core.App) error { &core.NumberField{Name: "downvotes"}, &core.TextField{Name: "parent_id"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createCommentVotes(app core.App) error { @@ -267,7 +280,7 @@ func createCommentVotes(app core.App) error { &core.TextField{Name: "session_id"}, &core.TextField{Name: "vote"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createTranslationJobs(app core.App) error { @@ -284,7 +297,7 @@ func createTranslationJobs(app core.App) error { &core.DateField{Name: "finished"}, &core.DateField{Name: "heartbeat_at"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createImportTasks(app core.App) error { @@ -311,7 +324,7 @@ func createImportTasks(app core.App) error { &core.DateField{Name: "finished"}, &core.DateField{Name: "heartbeat_at"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createNotifications(app core.App) error { @@ -323,7 +336,7 @@ func createNotifications(app core.App) error { &core.TextField{Name: "link"}, &core.BoolField{Name: "read"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createPushSubscriptions(app core.App) error { @@ -334,7 +347,7 @@ func createPushSubscriptions(app core.App) error { &core.TextField{Name: "p256dh", Required: true}, &core.TextField{Name: "auth", Required: true}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createAIJobs(app core.App) error { @@ -354,7 +367,7 @@ func createAIJobs(app core.App) error { &core.DateField{Name: "finished"}, &core.DateField{Name: "heartbeat_at"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createDiscoveryVotes(app core.App) error { @@ -365,7 +378,7 @@ func createDiscoveryVotes(app core.App) error { &core.TextField{Name: "slug", Required: true}, &core.TextField{Name: "action", Required: true}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createBookRatings(app core.App) error { @@ -376,7 +389,7 @@ func createBookRatings(app core.App) error { &core.TextField{Name: "slug", Required: true}, &core.NumberField{Name: "rating", Required: true}, ) - return app.Save(c) + return saveIfAbsent(app, c) } func createSiteConfig(app core.App) error { @@ -386,7 +399,7 @@ func createSiteConfig(app core.App) error { &core.TextField{Name: "logoAnimation"}, &core.TextField{Name: "eventLabel"}, ) - return app.Save(c) + return saveIfAbsent(app, c) } // createInitialSuperuser creates the first PocketBase superuser from env vars.