first with full handle over rpm
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
7948368573
commit
274ea454dd
50 changed files with 4309 additions and 0 deletions
78
internal/store/blocked_package.go
Normal file
78
internal/store/blocked_package.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BlockedPackage struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Name string
|
||||
Location string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type BlockedPackageStore interface {
|
||||
BlockPackages(ctx context.Context, repoID int64, pkgs []BlockedPackage) error
|
||||
UnblockPackages(ctx context.Context, repoID int64, ids []int64) error
|
||||
ListBlocked(ctx context.Context, repoID int64) ([]BlockedPackage, error)
|
||||
}
|
||||
|
||||
type SQLiteBlockedPackageStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewBlockedPackageStore(db *sql.DB) *SQLiteBlockedPackageStore {
|
||||
return &SQLiteBlockedPackageStore{db: db}
|
||||
}
|
||||
|
||||
func (s *SQLiteBlockedPackageStore) BlockPackages(ctx context.Context, repoID int64, pkgs []BlockedPackage) error {
|
||||
for _, p := range pkgs {
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT OR IGNORE INTO blocked_packages (repo_id, name, location) VALUES (?, ?, ?)`,
|
||||
repoID, p.Name, p.Location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteBlockedPackageStore) UnblockPackages(ctx context.Context, repoID int64, ids []int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
placeholders := strings.Join(strings.Fields(strings.Repeat("? ", len(ids))), ", ")
|
||||
query := fmt.Sprintf("DELETE FROM blocked_packages WHERE repo_id = ? AND id IN (%s)", placeholders)
|
||||
args := make([]any, 0, len(ids)+1)
|
||||
args = append(args, repoID)
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
_, err := s.db.ExecContext(ctx, query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SQLiteBlockedPackageStore) ListBlocked(ctx context.Context, repoID int64) ([]BlockedPackage, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, repo_id, name, location, created_at FROM blocked_packages WHERE repo_id = ? ORDER BY id ASC`,
|
||||
repoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var pkgs []BlockedPackage
|
||||
for rows.Next() {
|
||||
var p BlockedPackage
|
||||
if err := rows.Scan(&p.ID, &p.RepoID, &p.Name, &p.Location, &p.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
return pkgs, rows.Err()
|
||||
}
|
||||
109
internal/store/clone_job.go
Normal file
109
internal/store/clone_job.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CloneJobStatus string
|
||||
|
||||
const (
|
||||
CloneJobPending CloneJobStatus = "pending"
|
||||
CloneJobRunning CloneJobStatus = "running"
|
||||
CloneJobCompleted CloneJobStatus = "completed"
|
||||
CloneJobFailed CloneJobStatus = "failed"
|
||||
)
|
||||
|
||||
type CloneJob struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Status CloneJobStatus
|
||||
StartedAt *time.Time
|
||||
FinishedAt *time.Time
|
||||
Error *string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type CloneJobStore interface {
|
||||
CreateCloneJob(ctx context.Context, repoID int64) (int64, error)
|
||||
GetCloneJob(ctx context.Context, id int64) (*CloneJob, error)
|
||||
GetLatestCloneJob(ctx context.Context, repoID int64) (*CloneJob, error)
|
||||
HasRunningCloneJob(ctx context.Context, repoID int64) (bool, error)
|
||||
MarkCloneJobStarted(ctx context.Context, id int64) error
|
||||
MarkCloneJobFinished(ctx context.Context, id int64, status CloneJobStatus, errMsg *string) error
|
||||
}
|
||||
|
||||
type SQLiteCloneJobStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewCloneJobStore(db *sql.DB) *SQLiteCloneJobStore {
|
||||
return &SQLiteCloneJobStore{db: db}
|
||||
}
|
||||
|
||||
func (s *SQLiteCloneJobStore) CreateCloneJob(ctx context.Context, repoID int64) (int64, error) {
|
||||
res, err := s.db.ExecContext(ctx, `INSERT INTO clone_jobs (repo_id) VALUES (?)`, repoID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (s *SQLiteCloneJobStore) GetCloneJob(ctx context.Context, id int64) (*CloneJob, error) {
|
||||
row := s.db.QueryRowContext(ctx,
|
||||
`SELECT id, repo_id, status, started_at, finished_at, error, created_at FROM clone_jobs WHERE id = ?`, id)
|
||||
return scanCloneJob(row)
|
||||
}
|
||||
|
||||
func (s *SQLiteCloneJobStore) GetLatestCloneJob(ctx context.Context, repoID int64) (*CloneJob, error) {
|
||||
row := s.db.QueryRowContext(ctx,
|
||||
`SELECT id, repo_id, status, started_at, finished_at, error, created_at FROM clone_jobs WHERE repo_id = ? ORDER BY created_at DESC LIMIT 1`, repoID)
|
||||
return scanCloneJob(row)
|
||||
}
|
||||
|
||||
func (s *SQLiteCloneJobStore) HasRunningCloneJob(ctx context.Context, repoID int64) (bool, error) {
|
||||
var count int
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT COUNT(*) FROM clone_jobs WHERE repo_id = ? AND status IN ('pending','running')`, repoID,
|
||||
).Scan(&count)
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func (s *SQLiteCloneJobStore) MarkCloneJobStarted(ctx context.Context, id int64) error {
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`UPDATE clone_jobs SET status='running', started_at=CURRENT_TIMESTAMP WHERE id=?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SQLiteCloneJobStore) MarkCloneJobFinished(ctx context.Context, id int64, status CloneJobStatus, errMsg *string) error {
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`UPDATE clone_jobs SET status=?, finished_at=CURRENT_TIMESTAMP, error=? WHERE id=?`,
|
||||
status, errMsg, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func scanCloneJob(row *sql.Row) (*CloneJob, error) {
|
||||
var j CloneJob
|
||||
var startedAt, finishedAt sql.NullTime
|
||||
var errMsg sql.NullString
|
||||
|
||||
err := row.Scan(&j.ID, &j.RepoID, &j.Status, &startedAt, &finishedAt, &errMsg, &j.CreatedAt)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if startedAt.Valid {
|
||||
j.StartedAt = &startedAt.Time
|
||||
}
|
||||
if finishedAt.Valid {
|
||||
j.FinishedAt = &finishedAt.Time
|
||||
}
|
||||
if errMsg.Valid {
|
||||
j.Error = &errMsg.String
|
||||
}
|
||||
return &j, nil
|
||||
}
|
||||
88
internal/store/db.go
Normal file
88
internal/store/db.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/syonad/clonepack/config"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
//go:embed migrations/000001_init.up.sql
|
||||
var migration001 string
|
||||
|
||||
//go:embed migrations/000002_clone_jobs.up.sql
|
||||
var migration002 string
|
||||
|
||||
//go:embed migrations/000003_sync.up.sql
|
||||
var migration003 string
|
||||
|
||||
//go:embed migrations/000004_snapshot_packages.up.sql
|
||||
var migration004 string
|
||||
|
||||
//go:embed migrations/000005_blocked_packages.up.sql
|
||||
var migration005 string
|
||||
|
||||
//go:embed migrations/000006_blocked_packages_name.up.sql
|
||||
var migration006 string
|
||||
|
||||
func Open(cfg config.DBConfig) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", cfg.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open db: %w", err)
|
||||
}
|
||||
|
||||
if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil {
|
||||
return nil, fmt.Errorf("set WAL mode: %w", err)
|
||||
}
|
||||
if _, err := db.Exec("PRAGMA foreign_keys=ON"); err != nil {
|
||||
return nil, fmt.Errorf("enable foreign keys: %w", err)
|
||||
}
|
||||
|
||||
if err := runMigrations(db); err != nil {
|
||||
return nil, fmt.Errorf("migrations: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func runMigrations(db *sql.DB) error {
|
||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrations := []struct {
|
||||
version int
|
||||
sql string
|
||||
}{
|
||||
{1, migration001},
|
||||
{2, migration002},
|
||||
{3, migration003},
|
||||
{4, migration004},
|
||||
{5, migration005},
|
||||
{6, migration006},
|
||||
}
|
||||
|
||||
for _, m := range migrations {
|
||||
var count int
|
||||
row := db.QueryRow("SELECT COUNT(*) FROM schema_migrations WHERE version = ?", m.version)
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := db.Exec(m.sql); err != nil {
|
||||
return fmt.Errorf("migration %d: %w", m.version, err)
|
||||
}
|
||||
if _, err := db.Exec("INSERT INTO schema_migrations (version) VALUES (?)", m.version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
3
internal/store/migrations/000001_init.down.sql
Normal file
3
internal/store/migrations/000001_init.down.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE IF EXISTS snapshots;
|
||||
DROP TABLE IF EXISTS artifacts;
|
||||
DROP TABLE IF EXISTS repos;
|
||||
25
internal/store/migrations/000001_init.up.sql
Normal file
25
internal/store/migrations/000001_init.up.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
CREATE TABLE IF NOT EXISTS repos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
type TEXT NOT NULL CHECK(type IN ('apt','rpm','docker','binary')),
|
||||
source_url TEXT NOT NULL,
|
||||
frozen BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS artifacts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repo_id INTEGER NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
checksum TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS snapshots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repo_id INTEGER NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
||||
label TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
1
internal/store/migrations/000002_clone_jobs.down.sql
Normal file
1
internal/store/migrations/000002_clone_jobs.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS clone_jobs;
|
||||
11
internal/store/migrations/000002_clone_jobs.up.sql
Normal file
11
internal/store/migrations/000002_clone_jobs.up.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS clone_jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repo_id INTEGER NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
||||
status TEXT NOT NULL CHECK(status IN ('pending','running','completed','failed')) DEFAULT 'pending',
|
||||
started_at DATETIME,
|
||||
finished_at DATETIME,
|
||||
error TEXT,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_clone_jobs_repo_id ON clone_jobs(repo_id);
|
||||
1
internal/store/migrations/000003_sync.down.sql
Normal file
1
internal/store/migrations/000003_sync.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS pending_packages;
|
||||
18
internal/store/migrations/000003_sync.up.sql
Normal file
18
internal/store/migrations/000003_sync.up.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
ALTER TABLE repos ADD COLUMN sync_mode TEXT NOT NULL DEFAULT 'auto'
|
||||
CHECK(sync_mode IN ('auto', 'manual'));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pending_packages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repo_id INTEGER NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
arch TEXT NOT NULL,
|
||||
location TEXT NOT NULL,
|
||||
checksum TEXT NOT NULL,
|
||||
checksum_type TEXT NOT NULL,
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(repo_id, name, version, arch)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pending_packages_repo_id ON pending_packages(repo_id);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP INDEX IF EXISTS idx_snapshot_packages_snapshot_id;
|
||||
DROP TABLE IF EXISTS snapshot_packages;
|
||||
14
internal/store/migrations/000004_snapshot_packages.up.sql
Normal file
14
internal/store/migrations/000004_snapshot_packages.up.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
CREATE TABLE IF NOT EXISTS snapshot_packages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
arch TEXT NOT NULL,
|
||||
location TEXT NOT NULL,
|
||||
checksum TEXT NOT NULL,
|
||||
checksum_type TEXT NOT NULL,
|
||||
size INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_snapshot_packages_snapshot_id
|
||||
ON snapshot_packages(snapshot_id);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS blocked_packages;
|
||||
7
internal/store/migrations/000005_blocked_packages.up.sql
Normal file
7
internal/store/migrations/000005_blocked_packages.up.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS blocked_packages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repo_id INTEGER NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
||||
location TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(repo_id, location)
|
||||
);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- SQLite ne supporte pas DROP COLUMN avant 3.35 ; recréation de la table sans la colonne name
|
||||
CREATE TABLE blocked_packages_backup AS SELECT id, repo_id, location, created_at FROM blocked_packages;
|
||||
DROP TABLE blocked_packages;
|
||||
ALTER TABLE blocked_packages_backup RENAME TO blocked_packages;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE blocked_packages ADD COLUMN name TEXT NOT NULL DEFAULT '';
|
||||
92
internal/store/pending_package.go
Normal file
92
internal/store/pending_package.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PendingPackage struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Name string
|
||||
Version string
|
||||
Arch string
|
||||
Location string
|
||||
Checksum string
|
||||
ChecksumType string
|
||||
Size int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type PendingPackageStore interface {
|
||||
UpsertPending(ctx context.Context, pkgs []PendingPackage) error
|
||||
ListPending(ctx context.Context, repoID int64) ([]PendingPackage, error)
|
||||
DeletePending(ctx context.Context, ids []int64) error
|
||||
DeleteAllPending(ctx context.Context, repoID int64) error
|
||||
}
|
||||
|
||||
type SQLitePendingPackageStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewPendingPackageStore(db *sql.DB) *SQLitePendingPackageStore {
|
||||
return &SQLitePendingPackageStore{db: db}
|
||||
}
|
||||
|
||||
func (s *SQLitePendingPackageStore) UpsertPending(ctx context.Context, pkgs []PendingPackage) error {
|
||||
for _, p := range pkgs {
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT OR IGNORE INTO pending_packages
|
||||
(repo_id, name, version, arch, location, checksum, checksum_type, size)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
p.RepoID, p.Name, p.Version, p.Arch, p.Location, p.Checksum, p.ChecksumType, p.Size,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLitePendingPackageStore) ListPending(ctx context.Context, repoID int64) ([]PendingPackage, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, repo_id, name, version, arch, location, checksum, checksum_type, size, created_at
|
||||
FROM pending_packages WHERE repo_id = ? ORDER BY created_at ASC`, repoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var pkgs []PendingPackage
|
||||
for rows.Next() {
|
||||
var p PendingPackage
|
||||
if err := rows.Scan(&p.ID, &p.RepoID, &p.Name, &p.Version, &p.Arch,
|
||||
&p.Location, &p.Checksum, &p.ChecksumType, &p.Size, &p.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
return pkgs, rows.Err()
|
||||
}
|
||||
|
||||
func (s *SQLitePendingPackageStore) DeletePending(ctx context.Context, ids []int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
placeholders := strings.Join(strings.Fields(strings.Repeat("? ", len(ids))), ", ")
|
||||
query := fmt.Sprintf("DELETE FROM pending_packages WHERE id IN (%s)", placeholders)
|
||||
args := make([]any, len(ids))
|
||||
for i, id := range ids {
|
||||
args[i] = id
|
||||
}
|
||||
_, err := s.db.ExecContext(ctx, query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SQLitePendingPackageStore) DeleteAllPending(ctx context.Context, repoID int64) error {
|
||||
_, err := s.db.ExecContext(ctx, `DELETE FROM pending_packages WHERE repo_id = ?`, repoID)
|
||||
return err
|
||||
}
|
||||
107
internal/store/repo.go
Normal file
107
internal/store/repo.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type Repo struct {
|
||||
ID int64 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Type string `db:"type"`
|
||||
SourceURL string `db:"source_url"`
|
||||
Frozen bool `db:"frozen"`
|
||||
SyncMode string `db:"sync_mode"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
type RepoStore interface {
|
||||
CreateRepo(ctx context.Context, r *Repo) (int64, error)
|
||||
ListRepos(ctx context.Context) ([]Repo, error)
|
||||
GetRepo(ctx context.Context, id int64) (*Repo, error)
|
||||
DeleteRepo(ctx context.Context, id int64) error
|
||||
UpdateRepoSyncMode(ctx context.Context, id int64, mode string) error
|
||||
}
|
||||
|
||||
type SQLiteRepoStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewRepoStore(db *sql.DB) *SQLiteRepoStore {
|
||||
return &SQLiteRepoStore{db: db}
|
||||
}
|
||||
|
||||
func (s *SQLiteRepoStore) CreateRepo(ctx context.Context, r *Repo) (int64, error) {
|
||||
res, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO repos (name, type, source_url, frozen, sync_mode) VALUES (?, ?, ?, ?, ?)`,
|
||||
r.Name, r.Type, r.SourceURL, r.Frozen, r.SyncMode,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (s *SQLiteRepoStore) ListRepos(ctx context.Context) ([]Repo, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, name, type, source_url, frozen, sync_mode, created_at FROM repos ORDER BY created_at DESC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var repos []Repo
|
||||
for rows.Next() {
|
||||
var r Repo
|
||||
if err := rows.Scan(&r.ID, &r.Name, &r.Type, &r.SourceURL, &r.Frozen, &r.SyncMode, &r.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos = append(repos, r)
|
||||
}
|
||||
return repos, rows.Err()
|
||||
}
|
||||
|
||||
func (s *SQLiteRepoStore) GetRepo(ctx context.Context, id int64) (*Repo, error) {
|
||||
var r Repo
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT id, name, type, source_url, frozen, sync_mode, created_at FROM repos WHERE id = ?`, id,
|
||||
).Scan(&r.ID, &r.Name, &r.Type, &r.SourceURL, &r.Frozen, &r.SyncMode, &r.CreatedAt)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteRepoStore) DeleteRepo(ctx context.Context, id int64) error {
|
||||
res, err := s.db.ExecContext(ctx, `DELETE FROM repos WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteRepoStore) UpdateRepoSyncMode(ctx context.Context, id int64, mode string) error {
|
||||
res, err := s.db.ExecContext(ctx, `UPDATE repos SET sync_mode = ? WHERE id = ?`, mode, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
if n == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
144
internal/store/snapshot.go
Normal file
144
internal/store/snapshot.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Snapshot struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Label string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type SnapshotPackage struct {
|
||||
ID int64
|
||||
SnapshotID int64
|
||||
Name string
|
||||
Version string
|
||||
Arch string
|
||||
Location string
|
||||
Checksum string
|
||||
ChecksumType string
|
||||
Size int64
|
||||
}
|
||||
|
||||
type SnapshotStore interface {
|
||||
CreateSnapshot(ctx context.Context, repoID int64, label string) (int64, error)
|
||||
AddSnapshotPackages(ctx context.Context, snapshotID int64, pkgs []SnapshotPackage) error
|
||||
ListSnapshots(ctx context.Context, repoID int64) ([]Snapshot, error)
|
||||
GetSnapshot(ctx context.Context, id int64) (*Snapshot, error)
|
||||
GetSnapshotPackages(ctx context.Context, snapshotID int64) ([]SnapshotPackage, error)
|
||||
DeleteSnapshot(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
type SQLiteSnapshotStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewSnapshotStore(db *sql.DB) *SQLiteSnapshotStore {
|
||||
return &SQLiteSnapshotStore{db: db}
|
||||
}
|
||||
|
||||
func (s *SQLiteSnapshotStore) CreateSnapshot(ctx context.Context, repoID int64, label string) (int64, error) {
|
||||
res, err := s.db.ExecContext(ctx, `INSERT INTO snapshots (repo_id, label) VALUES (?, ?)`, repoID, label)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func (s *SQLiteSnapshotStore) AddSnapshotPackages(ctx context.Context, snapshotID int64, pkgs []SnapshotPackage) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
stmt, err := tx.PrepareContext(ctx, `INSERT INTO snapshot_packages
|
||||
(snapshot_id, name, version, arch, location, checksum, checksum_type, size)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, p := range pkgs {
|
||||
if _, err := stmt.ExecContext(ctx, snapshotID, p.Name, p.Version, p.Arch, p.Location, p.Checksum, p.ChecksumType, p.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *SQLiteSnapshotStore) ListSnapshots(ctx context.Context, repoID int64) ([]Snapshot, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, repo_id, label, created_at FROM snapshots WHERE repo_id = ? ORDER BY id DESC`, repoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var snaps []Snapshot
|
||||
for rows.Next() {
|
||||
var snap Snapshot
|
||||
if err := rows.Scan(&snap.ID, &snap.RepoID, &snap.Label, &snap.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snaps = append(snaps, snap)
|
||||
}
|
||||
return snaps, rows.Err()
|
||||
}
|
||||
|
||||
func (s *SQLiteSnapshotStore) GetSnapshot(ctx context.Context, id int64) (*Snapshot, error) {
|
||||
var snap Snapshot
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT id, repo_id, label, created_at FROM snapshots WHERE id = ?`, id).
|
||||
Scan(&snap.ID, &snap.RepoID, &snap.Label, &snap.CreatedAt)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &snap, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteSnapshotStore) GetSnapshotPackages(ctx context.Context, snapshotID int64) ([]SnapshotPackage, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, snapshot_id, name, version, arch, location, checksum, checksum_type, size
|
||||
FROM snapshot_packages WHERE snapshot_id = ?`, snapshotID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var pkgs []SnapshotPackage
|
||||
for rows.Next() {
|
||||
var p SnapshotPackage
|
||||
if err := rows.Scan(&p.ID, &p.SnapshotID, &p.Name, &p.Version, &p.Arch,
|
||||
&p.Location, &p.Checksum, &p.ChecksumType, &p.Size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
return pkgs, rows.Err()
|
||||
}
|
||||
|
||||
func (s *SQLiteSnapshotStore) DeleteSnapshot(ctx context.Context, id int64) error {
|
||||
res, err := s.db.ExecContext(ctx, `DELETE FROM snapshots WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue