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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user