From 8548c9b18bdc5ab26cef6ff50a369a96f68d30d0 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Wed, 6 May 2026 12:29:36 +0800 Subject: [PATCH] feat: add ResourcesRepo for standard_resources CRUD --- internal/storage/resources_repo.go | 125 ++++++++++++++++++++++++ internal/storage/resources_repo_test.go | 85 ++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 internal/storage/resources_repo.go create mode 100644 internal/storage/resources_repo_test.go diff --git a/internal/storage/resources_repo.go b/internal/storage/resources_repo.go new file mode 100644 index 0000000..151a99d --- /dev/null +++ b/internal/storage/resources_repo.go @@ -0,0 +1,125 @@ +package storage + +import ( + "database/sql" + "time" +) + +type StandardResourceRecord struct { + Name string + ResourceType string + Version string + SHA256 string + SizeBytes int64 + Description string + FilePath string + CreatedAt string + UpdatedAt string +} + +type ResourcesRepo struct { + db *sql.DB +} + +func NewResourcesRepo(db *sql.DB) *ResourcesRepo { + return &ResourcesRepo{db: db} +} + +func (r *ResourcesRepo) Save(item StandardResourceRecord) error { + if r == nil || r.db == nil { + return nil + } + now := time.Now().Format(time.RFC3339) + createdAt := item.CreatedAt + if createdAt == "" { + createdAt = now + } + updatedAt := item.UpdatedAt + if updatedAt == "" { + updatedAt = now + } + _, err := r.db.Exec(` +INSERT INTO standard_resources(name, resource_type, version, sha256, size_bytes, description, file_path, created_at, updated_at) +VALUES(?, ?, ?, ?, ?, ?, ?, COALESCE((SELECT created_at FROM standard_resources WHERE name = ?), ?), ?) +ON CONFLICT(name) DO UPDATE SET + resource_type=excluded.resource_type, + version=excluded.version, + sha256=excluded.sha256, + size_bytes=excluded.size_bytes, + description=excluded.description, + file_path=excluded.file_path, + updated_at=excluded.updated_at +`, item.Name, item.ResourceType, item.Version, item.SHA256, item.SizeBytes, item.Description, item.FilePath, item.Name, createdAt, updatedAt) + return err +} + +func (r *ResourcesRepo) List() ([]StandardResourceRecord, error) { + if r == nil || r.db == nil { + return nil, nil + } + rows, err := r.db.Query(` +SELECT name, resource_type, version, sha256, size_bytes, description, file_path, created_at, updated_at +FROM standard_resources +ORDER BY resource_type, name +`) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]StandardResourceRecord, 0) + for rows.Next() { + var item StandardResourceRecord + if err := rows.Scan( + &item.Name, + &item.ResourceType, + &item.Version, + &item.SHA256, + &item.SizeBytes, + &item.Description, + &item.FilePath, + &item.CreatedAt, + &item.UpdatedAt, + ); err != nil { + return nil, err + } + out = append(out, item) + } + return out, rows.Err() +} + +func (r *ResourcesRepo) ListByType(resourceType string) ([]StandardResourceRecord, error) { + if r == nil || r.db == nil { + return nil, nil + } + rows, err := r.db.Query(` +SELECT name, resource_type, version, sha256, size_bytes, description, file_path, created_at, updated_at +FROM standard_resources +WHERE resource_type = ? +ORDER BY name +`, resourceType) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]StandardResourceRecord, 0) + for rows.Next() { + var item StandardResourceRecord + if err := rows.Scan( + &item.Name, + &item.ResourceType, + &item.Version, + &item.SHA256, + &item.SizeBytes, + &item.Description, + &item.FilePath, + &item.CreatedAt, + &item.UpdatedAt, + ); err != nil { + return nil, err + } + out = append(out, item) + } + return out, rows.Err() +} diff --git a/internal/storage/resources_repo_test.go b/internal/storage/resources_repo_test.go new file mode 100644 index 0000000..609cdc3 --- /dev/null +++ b/internal/storage/resources_repo_test.go @@ -0,0 +1,85 @@ +package storage + +import ( + "path/filepath" + "testing" +) + +func TestResourcesRepo_SaveAndList(t *testing.T) { + store, err := OpenSQLite(filepath.Join(t.TempDir(), "app.db")) + if err != nil { + t.Fatalf("OpenSQLite: %v", err) + } + defer store.Close() + + repo := NewResourcesRepo(store.DB()) + err = repo.Save(StandardResourceRecord{ + Name: "face_gallery_v1", + ResourceType: "face_gallery", + Version: "auto", + SHA256: "abc123", + SizeBytes: 52428800, + Description: "默认人脸库", + FilePath: "resources/standard_resources/face_gallery_v1.db", + }) + if err != nil { + t.Fatalf("Save: %v", err) + } + + items, err := repo.List() + if err != nil { + t.Fatalf("List: %v", err) + } + if len(items) != 1 || items[0].ResourceType != "face_gallery" || items[0].Version != "auto" { + t.Fatalf("unexpected resources: %#v", items) + } +} + +func TestResourcesRepo_ListByType(t *testing.T) { + store, err := OpenSQLite(filepath.Join(t.TempDir(), "app.db")) + if err != nil { + t.Fatalf("OpenSQLite: %v", err) + } + defer store.Close() + + repo := NewResourcesRepo(store.DB()) + repo.Save(StandardResourceRecord{ + Name: "face_gallery_v1", ResourceType: "face_gallery", + Version: "v1", SHA256: "abc", + }) + repo.Save(StandardResourceRecord{ + Name: "ppe_dataset_v1", ResourceType: "dataset", + Version: "v2", SHA256: "def", + }) + + items, err := repo.ListByType("face_gallery") + if err != nil { + t.Fatalf("ListByType: %v", err) + } + if len(items) != 1 || items[0].Name != "face_gallery_v1" { + t.Fatalf("expected 1 face_gallery, got %#v", items) + } +} + +func TestResourcesRepo_SaveUpsert(t *testing.T) { + store, err := OpenSQLite(filepath.Join(t.TempDir(), "app.db")) + if err != nil { + t.Fatalf("OpenSQLite: %v", err) + } + defer store.Close() + + repo := NewResourcesRepo(store.DB()) + repo.Save(StandardResourceRecord{ + Name: "face_gallery_v1", ResourceType: "face_gallery", + Version: "v1", SHA256: "abc", + }) + repo.Save(StandardResourceRecord{ + Name: "face_gallery_v1", ResourceType: "face_gallery", + Version: "v2", SHA256: "xyz", + }) + + items, _ := repo.List() + if len(items) != 1 || items[0].Version != "v2" || items[0].SHA256 != "xyz" { + t.Fatalf("expected upserted record v2/xyz, got %#v", items) + } +}