fix: make migration 1 idempotent for existing installs

Each collection creator now skips creation if the collection already exists,
so deploying to an existing prod install no longer requires running
`migrate history-sync` manually. Migration 2 (missing fields) still applies
as normal since those fields genuinely don't exist yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Admin
2026-04-14 19:38:26 +05:00
parent fd0f2afe16
commit 38cf1c82a1

View File

@@ -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.