Build the complete artifact pillar with five packages: - artifacts: Artifact, Snapshot, Citation, Publisher types with Get/Store DB methods, tag/category management, metadata ops, YAML import - blob: content-addressable store (SHA256, hierarchical dir layout) - proto: protobuf definitions (common.proto, artifacts.proto) with buf linting and code generation - server: gRPC ArtifactService implementation (create/get artifacts, store/retrieve blobs, manage tags/categories, search by tag) All FK insertion ordering is correct (parent rows before children). Full test coverage across artifacts, blob, and server packages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
71 lines
1.8 KiB
Go
71 lines
1.8 KiB
Go
package artifacts
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"git.wntrmute.dev/kyle/exo/core"
|
|
)
|
|
|
|
// Publisher represents a publishing entity.
|
|
type Publisher struct {
|
|
ID string
|
|
Name string
|
|
Address string
|
|
}
|
|
|
|
// findPublisher looks up a publisher by name and address, returning its ID.
|
|
func findPublisher(ctx context.Context, tx *sql.Tx, name, address string) (string, error) {
|
|
var id string
|
|
row := tx.QueryRowContext(ctx,
|
|
`SELECT id FROM publishers WHERE name=? AND address=?`, name, address)
|
|
if err := row.Scan(&id); err != nil {
|
|
return "", err
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// Store persists a Publisher. If a publisher with the same name and address
|
|
// already exists, it reuses that record.
|
|
func (p *Publisher) Store(ctx context.Context, tx *sql.Tx) error {
|
|
if p.ID == "" {
|
|
id, err := findPublisher(ctx, tx, p.Name, p.Address)
|
|
if err == nil {
|
|
p.ID = id
|
|
return nil
|
|
}
|
|
if !errors.Is(err, sql.ErrNoRows) {
|
|
return fmt.Errorf("artifacts: failed to look up publisher: %w", err)
|
|
}
|
|
p.ID = core.NewUUID()
|
|
}
|
|
|
|
_, err := tx.ExecContext(ctx,
|
|
`INSERT INTO publishers (id, name, address) VALUES (?, ?, ?)`,
|
|
p.ID, p.Name, p.Address)
|
|
if err != nil {
|
|
return fmt.Errorf("artifacts: failed to store publisher: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get retrieves a Publisher by its ID.
|
|
func (p *Publisher) Get(ctx context.Context, tx *sql.Tx) (bool, error) {
|
|
if p.ID == "" {
|
|
return false, fmt.Errorf("artifacts: publisher missing ID: %w", core.ErrNoID)
|
|
}
|
|
|
|
row := tx.QueryRowContext(ctx,
|
|
`SELECT name, address FROM publishers WHERE id=?`, p.ID)
|
|
err := row.Scan(&p.Name, &p.Address)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("artifacts: failed to look up publisher: %w", err)
|
|
}
|
|
return true, nil
|
|
}
|