Goondex/internal/db/studio_store.go
Team Goon 2e747c6660 Initial release: v0.1.0-dev1
Clean slate initialization of Goondex - a fast, local-first media indexer.

Features:
- SQLite database with WAL mode and foreign keys
- Full schema for performers, studios, scenes, and tags
- Many-to-many relationships via junction tables
- CRUD stores for all entities with search capabilities
- CLI with performer/studio/scene search commands
- Pluggable scraper architecture with registry
- TPDB client stub (ready for implementation)
- Configuration system via YAML files
- Comprehensive .gitignore and documentation

Architecture:
- cmd/goondex: CLI application with Cobra
- cmd/goondexd: Daemon placeholder for v0.2.0
- internal/db: Database layer with stores
- internal/model: Clean data models
- internal/scraper: Scraper interface and TPDB client
- config/: YAML configuration templates

Database schema includes indexes on common query fields and uses
RFC3339 timestamps for consistency.

Built and tested successfully with Go 1.25.4.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 21:37:26 -05:00

145 lines
3.9 KiB
Go

package db
import (
"database/sql"
"fmt"
"time"
"git.leaktechnologies.dev/stu/Goondex/internal/model"
)
// StudioStore handles CRUD operations for studios
type StudioStore struct {
db *DB
}
// NewStudioStore creates a new studio store
func NewStudioStore(db *DB) *StudioStore {
return &StudioStore{db: db}
}
// Create inserts a new studio
func (s *StudioStore) Create(studio *model.Studio) error {
now := time.Now()
studio.CreatedAt = now
studio.UpdatedAt = now
result, err := s.db.conn.Exec(`
INSERT INTO studios (name, parent_id, image_path, image_url, description, source, source_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, studio.Name, studio.ParentID, studio.ImagePath, studio.ImageURL, studio.Description, studio.Source, studio.SourceID, studio.CreatedAt.Format(time.RFC3339), studio.UpdatedAt.Format(time.RFC3339))
if err != nil {
return fmt.Errorf("failed to create studio: %w", err)
}
id, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("failed to get last insert id: %w", err)
}
studio.ID = id
return nil
}
// GetByID retrieves a studio by ID
func (s *StudioStore) GetByID(id int64) (*model.Studio, error) {
studio := &model.Studio{}
var createdAt, updatedAt string
err := s.db.conn.QueryRow(`
SELECT id, name, parent_id, image_path, image_url, description, source, source_id, created_at, updated_at
FROM studios WHERE id = ?
`, id).Scan(&studio.ID, &studio.Name, &studio.ParentID, &studio.ImagePath, &studio.ImageURL, &studio.Description, &studio.Source, &studio.SourceID, &createdAt, &updatedAt)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("studio not found")
}
if err != nil {
return nil, fmt.Errorf("failed to get studio: %w", err)
}
studio.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
studio.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
return studio, nil
}
// Search searches for studios by name
func (s *StudioStore) Search(query string) ([]model.Studio, error) {
rows, err := s.db.conn.Query(`
SELECT id, name, parent_id, image_path, image_url, description, source, source_id, created_at, updated_at
FROM studios
WHERE name LIKE ?
ORDER BY name
`, "%"+query+"%")
if err != nil {
return nil, fmt.Errorf("failed to search studios: %w", err)
}
defer rows.Close()
var studios []model.Studio
for rows.Next() {
var studio model.Studio
var createdAt, updatedAt string
err := rows.Scan(&studio.ID, &studio.Name, &studio.ParentID, &studio.ImagePath, &studio.ImageURL, &studio.Description, &studio.Source, &studio.SourceID, &createdAt, &updatedAt)
if err != nil {
return nil, fmt.Errorf("failed to scan studio: %w", err)
}
studio.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
studio.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
studios = append(studios, studio)
}
return studios, nil
}
// Update updates an existing studio
func (s *StudioStore) Update(studio *model.Studio) error {
studio.UpdatedAt = time.Now()
result, err := s.db.conn.Exec(`
UPDATE studios
SET name = ?, parent_id = ?, image_path = ?, image_url = ?, description = ?, source = ?, source_id = ?, updated_at = ?
WHERE id = ?
`, studio.Name, studio.ParentID, studio.ImagePath, studio.ImageURL, studio.Description, studio.Source, studio.SourceID, studio.UpdatedAt.Format(time.RFC3339), studio.ID)
if err != nil {
return fmt.Errorf("failed to update studio: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rows == 0 {
return fmt.Errorf("studio not found")
}
return nil
}
// Delete deletes a studio by ID
func (s *StudioStore) Delete(id int64) error {
result, err := s.db.conn.Exec("DELETE FROM studios WHERE id = ?", id)
if err != nil {
return fmt.Errorf("failed to delete studio: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rows == 0 {
return fmt.Errorf("studio not found")
}
return nil
}