···2222 MediaType string
2323 ConfigDigest string
2424 ConfigSize int64
2525+ ArtifactType string // container-image, helm-chart, unknown
2526 CreatedAt time.Time
2627 // Annotations removed - now stored in repository_annotations table
2728}
···7576 CreatedAt time.Time
7677 HoldEndpoint string // Hold endpoint for health checking
7778 Reachable bool // Whether the hold endpoint is reachable
7979+ ArtifactType string // container-image, helm-chart, unknown
7880}
79818082// Repository represents an aggregated view of a user's repository
···108110109111// FeaturedRepository represents a repository in the featured section
110112type FeaturedRepository struct {
111111- OwnerDID string
112112- OwnerHandle string
113113- Repository string
114114- Title string
115115- Description string
116116- IconURL string
117117- StarCount int
118118- PullCount int
119119- IsStarred bool // Whether the current user has starred this repository
113113+ OwnerDID string
114114+ OwnerHandle string
115115+ Repository string
116116+ Title string
117117+ Description string
118118+ IconURL string
119119+ StarCount int
120120+ PullCount int
121121+ IsStarred bool // Whether the current user has starred this repository
122122+ ArtifactType string // container-image, helm-chart, unknown
120123}
121124122125// RepositoryWithStats combines repository data with statistics
···127130128131// RepoCardData contains all data needed to render a repository card
129132type RepoCardData struct {
130130- OwnerHandle string
131131- Repository string
132132- Title string
133133- Description string
134134- IconURL string
135135- StarCount int
136136- PullCount int
137137- IsStarred bool // Whether the current user has starred this repository
133133+ OwnerHandle string
134134+ Repository string
135135+ Title string
136136+ Description string
137137+ IconURL string
138138+ StarCount int
139139+ PullCount int
140140+ IsStarred bool // Whether the current user has starred this repository
141141+ ArtifactType string // container-image, helm-chart, unknown
138142}
139143140144// PlatformInfo represents platform information (OS/Architecture)
···163167 HasAttestations bool // true if manifest list contains attestation references
164168 Reachable bool // Whether the hold endpoint is reachable
165169 Pending bool // Whether health check is still in progress
170170+ // Note: ArtifactType is available via embedded Manifest struct
166171}
+39-18
pkg/appview/db/queries.go
···1313 return fmt.Sprintf("https://imgs.blue/%s/%s", did, cid)
1414}
15151616+// GetArtifactType determines the artifact type based on config media type
1717+// Returns: "helm-chart", "container-image", or "unknown"
1818+func GetArtifactType(configMediaType string) string {
1919+ switch {
2020+ case strings.Contains(configMediaType, "helm.config"):
2121+ return "helm-chart"
2222+ case strings.Contains(configMediaType, "oci.image.config") ||
2323+ strings.Contains(configMediaType, "docker.container.image"):
2424+ return "container-image"
2525+ case configMediaType == "":
2626+ // Manifest lists don't have a config - treat as container-image
2727+ return "container-image"
2828+ default:
2929+ return "unknown"
3030+ }
3131+}
3232+1633// escapeLikePattern escapes SQL LIKE wildcards (%, _) and backslash for safe searching.
1734// It also sanitizes the input to prevent injection attacks via special characters.
1835func escapeLikePattern(s string) string {
···5370 COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = u.did AND repository = t.repository), 0),
5471 t.created_at,
5572 m.hold_endpoint,
5656- COALESCE(rp.avatar_cid, '')
7373+ COALESCE(rp.avatar_cid, ''),
7474+ COALESCE(m.artifact_type, 'container-image')
5775 FROM tags t
5876 JOIN users u ON t.did = u.did
5977 JOIN manifests m ON t.did = m.did AND t.repository = m.repository AND t.digest = m.digest
···82100 var p Push
83101 var isStarredInt int
84102 var avatarCID string
8585- if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint, &avatarCID); err != nil {
103103+ if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint, &avatarCID, &p.ArtifactType); err != nil {
86104 return nil, 0, err
87105 }
88106 p.IsStarred = isStarredInt > 0
···133151 COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = u.did AND repository = t.repository), 0),
134152 t.created_at,
135153 m.hold_endpoint,
136136- COALESCE(rp.avatar_cid, '')
154154+ COALESCE(rp.avatar_cid, ''),
155155+ COALESCE(m.artifact_type, 'container-image')
137156 FROM tags t
138157 JOIN users u ON t.did = u.did
139158 JOIN manifests m ON t.did = m.did AND t.repository = m.repository AND t.digest = m.digest
···162181 var p Push
163182 var isStarredInt int
164183 var avatarCID string
165165- if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint, &avatarCID); err != nil {
184184+ if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint, &avatarCID, &p.ArtifactType); err != nil {
166185 return nil, 0, err
167186 }
168187 p.IsStarred = isStarredInt > 0
···274293 // Get manifests for this repo
275294 manifestRows, err := db.Query(`
276295 SELECT id, digest, hold_endpoint, schema_version, media_type,
277277- config_digest, config_size, created_at
296296+ config_digest, config_size, artifact_type, created_at
278297 FROM manifests
279298 WHERE did = ? AND repository = ?
280299 ORDER BY created_at DESC
···290309 m.Repository = r.Name
291310292311 if err := manifestRows.Scan(&m.ID, &m.Digest, &m.HoldEndpoint, &m.SchemaVersion,
293293- &m.MediaType, &m.ConfigDigest, &m.ConfigSize, &m.CreatedAt); err != nil {
312312+ &m.MediaType, &m.ConfigDigest, &m.ConfigSize, &m.ArtifactType, &m.CreatedAt); err != nil {
294313 manifestRows.Close()
295314 return nil, err
296315 }
···560579 _, err := db.Exec(`
561580 INSERT INTO manifests
562581 (did, repository, digest, hold_endpoint, schema_version, media_type,
563563- config_digest, config_size, created_at)
564564- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
582582+ config_digest, config_size, artifact_type, created_at)
583583+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
565584 ON CONFLICT(did, repository, digest) DO UPDATE SET
566585 hold_endpoint = excluded.hold_endpoint,
567586 schema_version = excluded.schema_version,
568587 media_type = excluded.media_type,
569588 config_digest = excluded.config_digest,
570570- config_size = excluded.config_size
589589+ config_size = excluded.config_size,
590590+ artifact_type = excluded.artifact_type
571591 `, manifest.DID, manifest.Repository, manifest.Digest, manifest.HoldEndpoint,
572592 manifest.SchemaVersion, manifest.MediaType, manifest.ConfigDigest,
573573- manifest.ConfigSize, manifest.CreatedAt)
593593+ manifest.ConfigSize, manifest.ArtifactType, manifest.CreatedAt)
574594575595 if err != nil {
576596 return 0, err
···931951 SELECT
932952 m.id, m.did, m.repository, m.digest, m.media_type,
933953 m.schema_version, m.created_at,
934934- m.config_digest, m.config_size, m.hold_endpoint,
954954+ m.config_digest, m.config_size, m.hold_endpoint, m.artifact_type,
935955 GROUP_CONCAT(DISTINCT t.tag) as tags,
936956 COUNT(DISTINCT mr.digest) as platform_count
937957 FROM manifests m
···964984 if err := rows.Scan(
965985 &m.ID, &m.DID, &m.Repository, &m.Digest, &m.MediaType,
966986 &m.SchemaVersion, &m.CreatedAt,
967967- &configDigest, &configSize, &m.HoldEndpoint,
987987+ &configDigest, &configSize, &m.HoldEndpoint, &m.ArtifactType,
968988 &tags, &m.PlatformCount,
969989 ); err != nil {
970990 return nil, err
···10621082 SELECT
10631083 m.id, m.did, m.repository, m.digest, m.media_type,
10641084 m.schema_version, m.created_at,
10651065- m.config_digest, m.config_size, m.hold_endpoint,
10851085+ m.config_digest, m.config_size, m.hold_endpoint, m.artifact_type,
10661086 GROUP_CONCAT(DISTINCT t.tag) as tags
10671087 FROM manifests m
10681088 LEFT JOIN tags t ON m.digest = t.digest AND m.did = t.did AND m.repository = t.repository
···10711091 `, did, repository, digest).Scan(
10721092 &m.ID, &m.DID, &m.Repository, &m.Digest, &m.MediaType,
10731093 &m.SchemaVersion, &m.CreatedAt,
10741074- &configDigest, &configSize, &m.HoldEndpoint,
10941094+ &configDigest, &configSize, &m.HoldEndpoint, &m.ArtifactType,
10751095 &tags,
10761096 )
10771097···13741394 // Get manifests for this repo
13751395 manifestRows, err := db.Query(`
13761396 SELECT id, digest, hold_endpoint, schema_version, media_type,
13771377- config_digest, config_size, created_at
13971397+ config_digest, config_size, artifact_type, created_at
13781398 FROM manifests
13791399 WHERE did = ? AND repository = ?
13801400 ORDER BY created_at DESC
···13901410 m.Repository = repository
1391141113921412 if err := manifestRows.Scan(&m.ID, &m.Digest, &m.HoldEndpoint, &m.SchemaVersion,
13931393- &m.MediaType, &m.ConfigDigest, &m.ConfigSize, &m.CreatedAt); err != nil {
14131413+ &m.MediaType, &m.ConfigDigest, &m.ConfigSize, &m.ArtifactType, &m.CreatedAt); err != nil {
13941414 manifestRows.Close()
13951415 return nil, err
13961416 }
···16751695 rs.pull_count,
16761696 rs.star_count,
16771697 COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = m.did AND repository = m.repository), 0),
16781678- COALESCE(rp.avatar_cid, '')
16981698+ COALESCE(rp.avatar_cid, ''),
16991699+ COALESCE(m.artifact_type, 'container-image')
16791700 FROM latest_manifests lm
16801701 JOIN manifests m ON lm.latest_id = m.id
16811702 JOIN users u ON m.did = u.did
···16981719 var avatarCID string
1699172017001721 if err := rows.Scan(&f.OwnerDID, &f.OwnerHandle, &f.Repository,
17011701- &f.Title, &f.Description, &f.IconURL, &f.PullCount, &f.StarCount, &isStarredInt, &avatarCID); err != nil {
17221722+ &f.Title, &f.Description, &f.IconURL, &f.PullCount, &f.StarCount, &isStarredInt, &avatarCID, &f.ArtifactType); err != nil {
17021723 return nil, err
17031724 }
17041725 f.IsStarred = isStarredInt > 0
+2
pkg/appview/db/schema.sql
···2727 media_type TEXT NOT NULL,
2828 config_digest TEXT,
2929 config_size INTEGER,
3030+ artifact_type TEXT NOT NULL DEFAULT 'container-image', -- container-image, helm-chart, unknown
3031 created_at TIMESTAMP NOT NULL,
3132 UNIQUE(did, repository, digest),
3233 FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE
···3435CREATE INDEX IF NOT EXISTS idx_manifests_did_repo ON manifests(did, repository);
3536CREATE INDEX IF NOT EXISTS idx_manifests_created_at ON manifests(created_at DESC);
3637CREATE INDEX IF NOT EXISTS idx_manifests_digest ON manifests(digest);
3838+CREATE INDEX IF NOT EXISTS idx_manifests_artifact_type ON manifests(artifact_type);
37393840CREATE TABLE IF NOT EXISTS repository_annotations (
3941 did TEXT NOT NULL,