package model import ( "crypto/sha256" "encoding/hex" "fmt" "io" "path/filepath" "strings" "time" "gorm.io/gorm" ) // Hash represents a content hash type Hash string // NewHash creates a new Hash from a string func NewHash(s string) (Hash, error) { if !strings.HasPrefix(s, "sha256:") { return "", fmt.Errorf("invalid hash format: %s", s) } return Hash(s), nil } // String returns the string representation of the hash func (h Hash) String() string { return string(h) } // Hex returns the hex part of the hash func (h Hash) Hex() (string, error) { if !strings.HasPrefix(string(h), "sha256:") { return "", fmt.Errorf("invalid hash format: %s", h) } return strings.TrimPrefix(string(h), "sha256:"), nil } // SHA256 computes the SHA256 hash of the given reader func SHA256(r io.Reader) (Hash, int64, error) { hasher := sha256.New() n, err := io.Copy(hasher, r) if err != nil { return "", 0, err } return Hash("sha256:" + hex.EncodeToString(hasher.Sum(nil))), n, nil } // RegistryRepository represents a repository in the registry type RegistryRepository struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"uniqueIndex;not null" json:"name"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` } // RegistryManifest represents a manifest in the registry type RegistryManifest struct { ID uint `gorm:"primaryKey" json:"id"` RepositoryID uint `gorm:"not null;index;uniqueIndex:idx_repo_digest" json:"repository_id"` Repository RegistryRepository `gorm:"foreignKey:RepositoryID" json:"repository"` Digest string `gorm:"not null;uniqueIndex:idx_repo_digest" json:"digest"` Tag string `gorm:"index" json:"tag"` ContentType string `gorm:"not null" json:"content_type"` Size int64 `gorm:"not null" json:"size"` Blob []byte `gorm:"type:blob" json:"blob"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` } // RegistryBlob represents a blob in the registry type RegistryBlob struct { ID uint `gorm:"primaryKey" json:"id"` Digest string `gorm:"uniqueIndex;not null" json:"digest"` Size int64 `gorm:"not null" json:"size"` Path string `gorm:"not null" json:"path"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` } // RegistryTag represents a tag in the registry type RegistryTag struct { ID uint `gorm:"primaryKey" json:"id"` RepositoryID uint `gorm:"not null;index" json:"repository_id"` Repository RegistryRepository `gorm:"foreignKey:RepositoryID" json:"repository"` Name string `gorm:"not null;index" json:"name"` ManifestID uint `gorm:"not null;index" json:"manifest_id"` Manifest RegistryManifest `gorm:"foreignKey:ManifestID" json:"manifest"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` } // RegistryUploadSession represents an upload session type RegistryUploadSession struct { ID uint `gorm:"primaryKey" json:"id"` RepositoryID uint `gorm:"not null;index" json:"repository_id"` Repository RegistryRepository `gorm:"foreignKey:RepositoryID" json:"repository"` SessionID string `gorm:"uniqueIndex;not null" json:"session_id"` Path string `gorm:"not null" json:"path"` Size int64 `gorm:"not null" json:"size"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` } // Tag represents a tag list response type Tag struct { Name string `json:"name"` Tags []string `json:"tags"` } // Catalog represents a catalog response type Catalog struct { Repositories []string `json:"repositories"` } // RepoSimpleManifest represents a simple manifest type RepoSimpleManifest struct { Blob []byte `json:"blob"` ContentType string `json:"content_type"` } // IndexManifest represents an index manifest type IndexManifest struct { SchemaVersion int `json:"schemaVersion"` MediaType string `json:"mediaType"` Manifests []IndexManifestEntry `json:"manifests"` } // IndexManifestEntry represents an entry in an index manifest type IndexManifestEntry struct { MediaType string `json:"mediaType"` Size int64 `json:"size"` Digest string `json:"digest"` Platform *Platform `json:"platform,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` } // Platform represents a platform specification type Platform struct { Architecture string `json:"architecture"` OS string `json:"os"` Variant string `json:"variant,omitempty"` } // GetBlobPath returns the file path for a blob func GetBlobPath(baseDir, digest string) string { hash := strings.TrimPrefix(digest, "sha256:") if len(hash) < 4 { return filepath.Join(baseDir, "registry", "blobs", "sha256", hash) } return filepath.Join(baseDir, "registry", "blobs", "sha256", hash[:2], hash[2:4], hash) } // GetUploadPath returns the file path for an upload session func GetUploadPath(baseDir, sessionID string) string { return filepath.Join(baseDir, "registry", "uploads", sessionID) }