Phases 5, 6, 8: OCI pull/push paths and admin REST API
Phase 5 (OCI pull): internal/oci/ package with manifest GET/HEAD by tag/digest, blob GET/HEAD with repo membership check, tag listing with OCI pagination, catalog listing. Multi-segment repo names via parseOCIPath() right-split routing. DB query layer in internal/db/repository.go. Phase 6 (OCI push): blob uploads (monolithic and chunked) with uploadManager tracking in-progress BlobWriters, manifest push implementing full ARCHITECTURE.md §5 flow in a single SQLite transaction (create repo, upsert manifest, populate manifest_blobs, atomic tag move). Digest verification on both blob commit and manifest push-by-digest. Phase 8 (admin REST): /v1 endpoints for auth (login/logout/health), repository management (list/detail/delete), policy CRUD with engine reload, audit log listing with filters, GC trigger/status stubs. RequireAdmin middleware, platform-standard error format. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
80
internal/db/upload.go
Normal file
80
internal/db/upload.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrUploadNotFound indicates the requested upload UUID does not exist.
|
||||
var ErrUploadNotFound = errors.New("db: upload not found")
|
||||
|
||||
// UploadRow represents a row in the uploads table.
|
||||
type UploadRow struct {
|
||||
ID int64
|
||||
UUID string
|
||||
RepositoryID int64
|
||||
ByteOffset int64
|
||||
}
|
||||
|
||||
// CreateUpload inserts a new upload row and returns its ID.
|
||||
func (d *DB) CreateUpload(uuid string, repoID int64) error {
|
||||
_, err := d.Exec(
|
||||
`INSERT INTO uploads (uuid, repository_id) VALUES (?, ?)`,
|
||||
uuid, repoID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: create upload: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUpload returns the upload with the given UUID.
|
||||
func (d *DB) GetUpload(uuid string) (*UploadRow, error) {
|
||||
var u UploadRow
|
||||
err := d.QueryRow(
|
||||
`SELECT id, uuid, repository_id, byte_offset FROM uploads WHERE uuid = ?`, uuid,
|
||||
).Scan(&u.ID, &u.UUID, &u.RepositoryID, &u.ByteOffset)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrUploadNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("db: get upload: %w", err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// UpdateUploadOffset sets the byte_offset for an upload.
|
||||
func (d *DB) UpdateUploadOffset(uuid string, offset int64) error {
|
||||
result, err := d.Exec(
|
||||
`UPDATE uploads SET byte_offset = ? WHERE uuid = ?`,
|
||||
offset, uuid,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: update upload offset: %w", err)
|
||||
}
|
||||
n, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: update upload offset rows affected: %w", err)
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrUploadNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUpload removes the upload row with the given UUID.
|
||||
func (d *DB) DeleteUpload(uuid string) error {
|
||||
result, err := d.Exec(`DELETE FROM uploads WHERE uuid = ?`, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: delete upload: %w", err)
|
||||
}
|
||||
n, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("db: delete upload rows affected: %w", err)
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrUploadNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user