Files
at-container-registry/pkg/appview/db/models.go
T
Evan JarrettandClaude Opus 5 454a6bad3d db: key manifests by manifest_key and drop the rowid
Completes the swap 0033 set up. layers and manifest_references move onto
manifest_key and manifests.id is gone, which removes the last node-allocated
identifier in the AppView schema.

Statement order in 0034 is load-bearing. With foreign keys on, DROP TABLE
performs an implicit DELETE FROM, so dropping manifests while layers still holds
an ON DELETE CASCADE reference deletes every layer row. Migration 0009 did
exactly that; it went unnoticed because the Jetstream backfill rebuilds layers
from PDS records, so the damage healed itself. PRAGMA foreign_keys is no help:
it is a no-op inside a transaction and migrations run in one. So the new
children are built pointing at manifests_new, the old children are dropped
first, and only then is the old manifests table dropped, by which point nothing
references it. Verified both behaviors before relying on them.

manifest_key is declared NOT NULL as well as PRIMARY KEY, because in SQLite a
PRIMARY KEY column still accepts NULL unless it is INTEGER PRIMARY KEY. That
constraint immediately caught four test helpers inserting manifests without one.

Five queries used MAX(id) as "the newest manifest in this repo", which I had
previously reported as absent after grepping only for ORDER BY. A derived key
has no ordering, so recency now comes from created_at with manifest_key as a
deterministic tiebreak. This is a real behavior change, and a fix: the two
disagree whenever a manifest is indexed out of order, which the backfill does
routinely, and created_at is the push time these queries always wanted. Both
directions are tested, including that ties resolve the same way every run.

InsertManifest and BatchInsertManifests no longer read anything back. The key is
derived from (did, repository, digest), so the writer knows it before the
statement runs: the select-back, its per-DID IN list, and the "manifest missing
id after batch insert" branch all go away, along with the UNIQUE-conflict
fallback that existed only to recover a rowid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:44:41 -05:00

202 lines
6.2 KiB
Go

package db
import "time"
// User represents a user in the system
type User struct {
DID string
Handle string
PDSEndpoint string
Avatar string
DefaultHoldDID string
OciClient string
RegistryDomain string
LastSeen time.Time
}
// Manifest represents an OCI manifest stored in the cache
type Manifest struct {
Key string // node-independent identity; see ManifestKey
DID string
Repository string
Digest string
HoldEndpoint string
SchemaVersion int
MediaType string
ConfigDigest string
ConfigSize int64
ArtifactType string // container-image, helm-chart, unknown
SubjectDigest string // digest of the parent manifest (for attestations/referrers)
CreatedAt time.Time
// Annotations removed - now stored in repository_annotations table
}
// Layer represents a layer in a manifest
type Layer struct {
ManifestKey string
Digest string
Size int64
MediaType string
LayerIndex int
Annotations map[string]string // JSON-encoded layer annotations (e.g. in-toto predicate type)
}
// ManifestReference represents a reference to a manifest in a manifest list/index
type ManifestReference struct {
ManifestKey string
Digest string
Size int64
MediaType string
PlatformArchitecture string
PlatformOS string
PlatformVariant string
PlatformOSVersion string
IsAttestation bool // true if vnd.docker.reference.type = "attestation-manifest"
ReferenceIndex int
}
// Tag represents a tag pointing to a manifest
type Tag struct {
DID string
Repository string
Tag string
Digest string
CreatedAt time.Time
}
// Repository represents an aggregated view of a user's repository
type Repository struct {
Name string
TagCount int
ManifestCount int
LastPush time.Time
Tags []Tag
Manifests []Manifest
Title string
Description string
SourceURL string
DocumentationURL string
Licenses string
IconURL string
ReadmeURL string
Version string
}
// RepositoryStats represents statistics for a repository
type RepositoryStats struct {
DID string `json:"did"`
Repository string `json:"repository"`
StarCount int `json:"star_count"` // Calculated from stars table, not stored
PullCount int `json:"pull_count"`
LastPull *time.Time `json:"last_pull,omitempty"`
PushCount int `json:"push_count"`
LastPush *time.Time `json:"last_push,omitempty"`
}
// DailyStats represents daily pull/push statistics for a repository
type DailyStats struct {
DID string `json:"did"`
Repository string `json:"repository"`
Date string `json:"date"`
PullCount int `json:"pull_count"`
PushCount int `json:"push_count"`
}
// RepositoryWithStats combines repository data with statistics
type RepositoryWithStats struct {
Repository
Stats RepositoryStats
}
// RepoCardData contains all data needed to render a repository card
type RepoCardData struct {
OwnerHandle string
OwnerAvatarURL string // Owner's profile avatar URL (fallback when no repo icon)
Repository string
Title string
Description string
IconURL string
StarCount int
PullCount int
IsStarred bool // Whether the current user has starred this repository
ArtifactType string // container-image, helm-chart, unknown
Tag string // Latest tag name (e.g., "latest", "v1.0.0")
Digest string // Latest manifest digest (sha256:...)
LastUpdated time.Time // When the repository was last pushed to
RegistryURL string // Registry URL for docker commands (e.g., "atcr.io" or "127.0.0.1:5000")
OciClient string // Preferred OCI client for pull commands (e.g., "docker", "podman")
}
// SetRegistryURL sets the RegistryURL field on all cards in the slice
func SetRegistryURL(cards []RepoCardData, registryURL string) {
for i := range cards {
cards[i].RegistryURL = registryURL
}
}
// SetOciClient sets the OciClient field on all cards in the slice
func SetOciClient(cards []RepoCardData, ociClient string) {
for i := range cards {
cards[i].OciClient = ociClient
}
}
// PlatformInfo represents platform information (OS/Architecture)
type PlatformInfo struct {
OS string
Architecture string
Variant string
OSVersion string
Digest string // child platform manifest digest (for manifest lists)
HoldEndpoint string // hold endpoint for this platform manifest
CompressedSize int64 // sum of layer sizes (compressed)
}
// TagWithPlatforms extends Tag with platform information
type TagWithPlatforms struct {
Tag
HoldEndpoint string // hold endpoint from the tag's own manifest
Platforms []PlatformInfo
IsMultiArch bool
HasAttestations bool // true if manifest list contains attestation references
ArtifactType string // container-image, helm-chart, unknown
CompressedSize int64 // sum of layer sizes for single-arch tags
}
// ManifestWithMetadata extends Manifest with tags and platform information
type ManifestWithMetadata struct {
Manifest
Tags []string
Platforms []PlatformInfo
PlatformCount int
IsManifestList bool
HasAttestations bool // true if manifest list contains attestation references
Reachable bool // Whether the hold endpoint is reachable
Pending bool // Whether health check is still in progress
// Note: ArtifactType is available via embedded Manifest struct
}
// ManifestEntry is a unified view model for the tags tab.
// Every entry is a manifest — labeled by tag name or digest.
type ManifestEntry struct {
Label string // tag name, or digest if untagged
Digest string // manifest digest
IsTagged bool
CreatedAt time.Time
HoldEndpoint string
Platforms []PlatformInfo
IsMultiArch bool
HasAttestations bool
ArtifactType string
CompressedSize int64 // for single-arch
}
// AttestationDetail represents an attestation manifest and its layers
type AttestationDetail struct {
Digest string
MediaType string // attestation manifest media type
Size int64
HoldEndpoint string // hold DID/URL where blobs are stored
Layers []Layer
}