pkg/seed
Seed data for CoreAPI's own coreapi database (roles, orgs, layouts, and
domain starter data like media transcoding profiles). Runs unconditionally
on every server boot via seed.Run(db.GetDB()), called from main.go
right after db.RunMigrationMain().
Not related to the ticketing (johorzoo) database's SQL dump files โ see
Migrations.
Entry point
pkg/seed/00-seed.go โ Run(db) calls each domain's seedX(db) function
in a fixed order (media first, then platform/roles/apps, then
workspace/layout/playlist data, then devices/teams/channels). Order
matters where one seed's rows reference another's โ e.g. media profiles
before anything that might reference a default profile ID.
Two seeding patterns in this package โ know which one a file uses
Most seed files (apps.go, roles.go, tags.go, workspaces.go,
teams.go, venue.go, locations.go, endpoints.go, layout.go,
playlists.go, mediachannel.go, devices.go, organization.go) use
the upsert() helper (00-seed.go) or an explicit Firstโ
continue-if-found check (transcoding_presets.go, the Playback
domain's own preset seed):
func upsert(db *gorm.DB, table string, value interface{}, conflictCols ...string) {
db.Clauses(clause.OnConflict{Columns: cols, DoNothing: true}).Create(value)
}
This is insert-if-not-exists, not a real upsert โ the name is historical. If you change a seed row's values in code and redeploy, an existing row with the same conflict key is left exactly as it was first created. This is fine for genuinely one-time bootstrap data (a platform org, a default role), but it silently does nothing for anything you expect to keep tuning during development.
mediatranscoding_presets.go uses a different, newer pattern โ
converge by Code:
func seedMediaProfile(db *gorm.DB, s mediaProfileSeed) {
// found by Code โ map-based Updates() converges name/category/isDefault
// and the CURRENT version's renditions/settings/segmentSecs to match
// this file, in place โ no new version row inserted on reseed.
// not found โ creates profile + version 1.
}
Every boot converges the DB row to exactly what the Go file currently
says. Use this pattern (not upsert()) for anything you expect to keep
editing as defaults shift during development โ see
Transcoding ยง Seeded Profiles
for the actual current values.
Pruning rows removed from the seed file
Converging by Code only handles a Code that's still present in
the file. Deleting or renaming an entry doesn't remove its old row โ
nothing before this was tracking "codes no longer defined here at all."
seedMediaTranscodingProfiles now tracks every Code it seeds in a
given run and calls pruneObsoleteMediaProfiles(db, seededCodes) at the
end:
// Deletes (hard, Unscoped) any TranscodingProfile with a non-null Code
// not in the current run's seeded list. Non-null Code is the signal โ
// CRUD-created profiles never set one, so this never touches anything
// created through the UI.
func pruneObsoleteMediaProfiles(db *gorm.DB, currentCodes []string) { ... }
Hard-deletes rather than soft-deletes deliberately โ Code has a unique
index, and a soft-deleted row would still occupy that value, so
reintroducing the same Code later would fail the unique constraint
instead of reseeding cleanly.
This runs on every boot, unconditionally, in every environment
Unlike the hard-reset tool below, pruning is not gated behind any
flag. Today that's safe because every seeded profile is still a
"starter default." But editing a seeded profile for real through the
CRUD UI (UpdateTranscodingProfile) does not clear its Code โ
so a profile someone is actively relying on can still be tagged with
its original seed Code. If that Code is later renamed or removed
from the seed file for an unrelated reason, the live profile is
silently hard-deleted on the next deploy. Treat a Code as a
permanent identity once anything real depends on the row it points
at โ don't repurpose or remove one casually once this reaches an
environment where that matters. If this needs to run in production
one day, gate pruning behind the same flag as the hard-reset tool
below rather than leaving it unconditional.
Companion tool: hard-resetting a domain's tables entirely
For schema churn heavy enough that converge-by-Code isn't enough โ
you're changing column shapes, not just seed values โ pkg/db has
HardResetTables(tables ...string): drops the given tables (CASCADE)
so the next AutoMigrate recreates them fresh from the current struct
shape, instead of trying (and sometimes failing) to ALTER an existing
one into it. See Migrations ยง Hard-resetting a
domain
for the mechanics and safety notes, and domain/media's
guide for the one live example
(MEDIA_HARD_RESET=true, media domain only).
This wipes all data in the listed tables โ only ever enable it in a disposable environment (staging, pre-launch), gated behind an env var that defaults off.