···3737 return nil, err
3838 }
39394040- // Create schema from embedded SQL file
4141- if _, err := db.Exec(schemaSQL); err != nil {
4242- return nil, err
4040+ // Check if this is an existing database with migrations applied
4141+ isExisting, err := hasAppliedMigrations(db)
4242+ if err != nil {
4343+ return nil, fmt.Errorf("failed to check database state: %w", err)
4444+ }
4545+4646+ if isExisting {
4747+ // Existing database: skip schema.sql, only run pending migrations
4848+ slog.Debug("Existing database detected, skipping schema.sql")
4949+ } else {
5050+ // Fresh database: apply schema.sql
5151+ slog.Info("Fresh database detected, applying schema")
5252+ if err := applySchema(db); err != nil {
5353+ return nil, err
5454+ }
4355 }
44564557 // Run migrations unless skipped
5858+ // For fresh databases, migrations are recorded but not executed (schema.sql is already complete)
4659 if !skipMigrations {
4747- if err := runMigrations(db); err != nil {
6060+ if err := runMigrations(db, !isExisting); err != nil {
4861 return nil, err
4962 }
5063 }
···5265 return db, nil
5366}
54676868+// hasAppliedMigrations checks if this is an existing database with migrations applied
6969+func hasAppliedMigrations(db *sql.DB) (bool, error) {
7070+ // Check if schema_migrations table exists
7171+ var count int
7272+ err := db.QueryRow(`
7373+ SELECT COUNT(*) FROM sqlite_master
7474+ WHERE type='table' AND name='schema_migrations'
7575+ `).Scan(&count)
7676+ if err != nil {
7777+ return false, err
7878+ }
7979+ if count == 0 {
8080+ return false, nil // No migrations table = fresh DB
8181+ }
8282+8383+ // Table exists, check if it has entries
8484+ err = db.QueryRow("SELECT COUNT(*) FROM schema_migrations").Scan(&count)
8585+ if err != nil {
8686+ return false, err
8787+ }
8888+ return count > 0, nil
8989+}
9090+9191+// applySchema executes schema.sql for fresh databases
9292+func applySchema(db *sql.DB) error {
9393+ for _, stmt := range splitSQLStatements(schemaSQL) {
9494+ if _, err := db.Exec(stmt); err != nil {
9595+ return fmt.Errorf("failed to apply schema: %w", err)
9696+ }
9797+ }
9898+ return nil
9999+}
100100+55101// Migration represents a database migration
56102type Migration struct {
57103 Version int
···61107}
6210863109// runMigrations applies any pending database migrations
6464-func runMigrations(db *sql.DB) error {
110110+// If freshDB is true, migrations are recorded but not executed (schema.sql already includes their changes)
111111+func runMigrations(db *sql.DB, freshDB bool) error {
65112 // Load migrations from files
66113 migrations, err := loadMigrations()
67114 if err != nil {
···86133 continue
87134 }
881358989- // Apply migration in a transaction
136136+ if freshDB {
137137+ // Fresh database: schema.sql already has everything, just record the migration
138138+ slog.Debug("Recording migration as applied (fresh DB)", "version", m.Version, "name", m.Name)
139139+ if _, err := db.Exec("INSERT INTO schema_migrations (version) VALUES (?)", m.Version); err != nil {
140140+ return fmt.Errorf("failed to record migration %d: %w", m.Version, err)
141141+ }
142142+ continue
143143+ }
144144+145145+ // Existing database: apply migration in a transaction
90146 slog.Info("Applying migration", "version", m.Version, "name", m.Name, "description", m.Description)
9114792148 tx, err := db.Begin()
+6-3
pkg/appview/handlers/repository.go
···231231 }
232232 }
233233234234- // Determine dominant artifact type from manifests
234234+ // Determine artifact type for header section from first tag
235235+ // This is used for the "Pull this image/chart" header command
235236 artifactType := "container-image"
236236- if len(manifests) > 0 {
237237- // Use the most recent manifest's artifact type
237237+ if len(tagsWithPlatforms) > 0 {
238238+ artifactType = tagsWithPlatforms[0].ArtifactType
239239+ } else if len(manifests) > 0 {
240240+ // Fallback to manifests if no tags
238241 artifactType = manifests[0].ArtifactType
239242 }
240243
+6
pkg/appview/storage/manifest_store.go
···124124 return "", fmt.Errorf("failed to create manifest record: %w", err)
125125 }
126126127127+ // OCI spec allows omitting mediaType from the manifest body (inferred from Content-Type header)
128128+ // Helm charts typically omit it, so use the media type from the request if body is empty
129129+ if manifestRecord.MediaType == "" && mediaType != "" {
130130+ manifestRecord.MediaType = mediaType
131131+ }
132132+127133 // Set the blob reference, hold DID, and hold endpoint
128134 manifestRecord.ManifestBlob = blobRef
129135 manifestRecord.HoldDID = s.ctx.HoldDID // Primary reference (DID)