package registry import ( "context" "database/sql" "strings" "reanimator/internal/domain" "reanimator/internal/idgen" ) type ComponentRepository struct { db *sql.DB idgen *idgen.Generator } func NewComponentRepository(db *sql.DB) *ComponentRepository { return &ComponentRepository{ db: db, idgen: idgen.NewGenerator(db), } } func (r *ComponentRepository) Create(ctx context.Context, component domain.Component) (domain.Component, error) { id, err := r.idgen.Generate(ctx, idgen.Component) if err != nil { return domain.Component{}, err } _, err = r.db.ExecContext(ctx, `INSERT INTO parts (id, vendor, model, vendor_serial, first_seen_at) VALUES (?, ?, ?, ?, ?)`, id, component.Vendor, component.Model, component.VendorSerial, component.FirstSeenAt, ) if err != nil { return domain.Component{}, classifyError(err) } return r.Get(ctx, id) } func (r *ComponentRepository) Get(ctx context.Context, id string) (domain.Component, error) { var component domain.Component var vendor sql.NullString var model sql.NullString var firstSeenAt sql.NullTime row := r.db.QueryRowContext(ctx, `SELECT id, vendor, model, vendor_serial, first_seen_at, created_at, updated_at FROM parts WHERE id = ?`, id, ) if err := row.Scan(&component.ID, &vendor, &model, &component.VendorSerial, &firstSeenAt, &component.CreatedAt, &component.UpdatedAt); err != nil { if err == sql.ErrNoRows { return domain.Component{}, ErrNotFound } return domain.Component{}, err } component.Vendor = nullStringToPtr(vendor) component.Model = nullStringToPtr(model) component.FirstSeenAt = nullTimeToPtr(firstSeenAt) return component, nil } func (r *ComponentRepository) GetByVendorSerial(ctx context.Context, vendorSerial string) (domain.Component, error) { vendorSerial = strings.TrimSpace(vendorSerial) if vendorSerial == "" { return domain.Component{}, ErrNotFound } var component domain.Component var vendor sql.NullString var model sql.NullString var firstSeenAt sql.NullTime row := r.db.QueryRowContext(ctx, `SELECT id, vendor, model, vendor_serial, first_seen_at, created_at, updated_at FROM parts WHERE vendor_serial = ?`, vendorSerial, ) if err := row.Scan(&component.ID, &vendor, &model, &component.VendorSerial, &firstSeenAt, &component.CreatedAt, &component.UpdatedAt); err != nil { if err == sql.ErrNoRows { return domain.Component{}, ErrNotFound } return domain.Component{}, err } component.Vendor = nullStringToPtr(vendor) component.Model = nullStringToPtr(model) component.FirstSeenAt = nullTimeToPtr(firstSeenAt) return component, nil } func (r *ComponentRepository) List(ctx context.Context) ([]domain.Component, error) { rows, err := r.db.QueryContext(ctx, `SELECT id, vendor, model, vendor_serial, first_seen_at, created_at, updated_at FROM parts ORDER BY created_at DESC`, ) if err != nil { return nil, err } defer rows.Close() parts := make([]domain.Component, 0) for rows.Next() { var component domain.Component var vendor sql.NullString var model sql.NullString var firstSeenAt sql.NullTime if err := rows.Scan(&component.ID, &vendor, &model, &component.VendorSerial, &firstSeenAt, &component.CreatedAt, &component.UpdatedAt); err != nil { return nil, err } component.Vendor = nullStringToPtr(vendor) component.Model = nullStringToPtr(model) component.FirstSeenAt = nullTimeToPtr(firstSeenAt) parts = append(parts, component) } if err := rows.Err(); err != nil { return nil, err } return parts, nil } func (r *ComponentRepository) Update(ctx context.Context, component domain.Component) (domain.Component, error) { result, err := r.db.ExecContext(ctx, `UPDATE parts SET vendor = ?, model = ?, vendor_serial = ? WHERE id = ?`, component.Vendor, component.Model, strings.TrimSpace(component.VendorSerial), component.ID, ) if err != nil { return domain.Component{}, classifyError(err) } affected, err := result.RowsAffected() if err != nil { return domain.Component{}, err } if affected == 0 { return domain.Component{}, ErrNotFound } return r.Get(ctx, component.ID) }