package postgres import ( "context" "database/sql" "fmt" "time" "github.com/google/uuid" "github.com/ocdp/cluster-service/internal/domain/entity" "github.com/ocdp/cluster-service/internal/domain/repository" "github.com/ocdp/cluster-service/internal/pkg/crypto" ) type RegistryRepository struct { db *DB encryptor crypto.Encryptor } func NewRegistryRepository(db *DB, encryptor crypto.Encryptor) repository.RegistryRepository { return &RegistryRepository{db: db, encryptor: encryptor} } func (r *RegistryRepository) Create(ctx context.Context, registry *entity.Registry) error { if registry.ID == "" { registry.ID = uuid.New().String() } encryptedPassword, err := r.encryptor.Encrypt(registry.Password) if err != nil { return fmt.Errorf("failed to encrypt password: %w", err) } query := ` INSERT INTO registries (id, workspace_id, owner_id, visibility, name, url, description, username, password, insecure, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ` _, err = r.db.conn.ExecContext(ctx, query, registry.ID, registry.WorkspaceID, registry.OwnerID, registry.Visibility, registry.Name, registry.URL, registry.Description, registry.Username, encryptedPassword, registry.Insecure, registry.CreatedAt, registry.UpdatedAt, ) if err != nil { return fmt.Errorf("failed to create registry: %w", err) } return nil } func (r *RegistryRepository) GetByID(ctx context.Context, id string) (*entity.Registry, error) { return r.get(ctx, "id = $1", id) } func (r *RegistryRepository) GetByName(ctx context.Context, name string) (*entity.Registry, error) { return r.get(ctx, "name = $1", name) } func (r *RegistryRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Registry, error) { query := fmt.Sprintf(` SELECT id, workspace_id, owner_id, visibility, name, url, description, username, password, insecure, created_at, updated_at FROM registries WHERE %s `, where) rows, err := r.db.conn.QueryContext(ctx, query, arg) if err != nil { return nil, fmt.Errorf("failed to get registry: %w", err) } defer rows.Close() if !rows.Next() { return nil, entity.ErrRegistryNotFound } registry, err := r.scanRegistry(rows) if err != nil { return nil, err } return registry, nil } func (r *RegistryRepository) Update(ctx context.Context, registry *entity.Registry) error { registry.UpdatedAt = time.Now() encryptedPassword, err := r.encryptor.Encrypt(registry.Password) if err != nil { return fmt.Errorf("failed to encrypt password: %w", err) } query := ` UPDATE registries SET workspace_id = $1, owner_id = $2, visibility = $3, name = $4, url = $5, description = $6, username = $7, password = $8, insecure = $9, updated_at = $10 WHERE id = $11 ` result, err := r.db.conn.ExecContext(ctx, query, registry.WorkspaceID, registry.OwnerID, registry.Visibility, registry.Name, registry.URL, registry.Description, registry.Username, encryptedPassword, registry.Insecure, registry.UpdatedAt, registry.ID, ) if err != nil { return fmt.Errorf("failed to update registry: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get affected rows: %w", err) } if rows == 0 { return entity.ErrRegistryNotFound } return nil } func (r *RegistryRepository) Delete(ctx context.Context, id string) error { result, err := r.db.conn.ExecContext(ctx, `DELETE FROM registries WHERE id = $1`, id) if err != nil { return fmt.Errorf("failed to delete registry: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get affected rows: %w", err) } if rows == 0 { return entity.ErrRegistryNotFound } return nil } func (r *RegistryRepository) List(ctx context.Context) ([]*entity.Registry, error) { query := ` SELECT id, workspace_id, owner_id, visibility, name, url, description, username, password, insecure, created_at, updated_at FROM registries ORDER BY created_at DESC ` rows, err := r.db.conn.QueryContext(ctx, query) if err != nil { return nil, fmt.Errorf("failed to list registries: %w", err) } defer rows.Close() registries := make([]*entity.Registry, 0) for rows.Next() { registry, err := r.scanRegistry(rows) if err != nil { return nil, err } registries = append(registries, registry) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("rows iteration error: %w", err) } return registries, nil } type registryScanner interface { Scan(dest ...interface{}) error } func (r *RegistryRepository) scanRegistry(scanner registryScanner) (*entity.Registry, error) { registry := &entity.Registry{} var encryptedPassword sql.NullString err := scanner.Scan( ®istry.ID, ®istry.WorkspaceID, ®istry.OwnerID, ®istry.Visibility, ®istry.Name, ®istry.URL, ®istry.Description, ®istry.Username, &encryptedPassword, ®istry.Insecure, ®istry.CreatedAt, ®istry.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan registry: %w", err) } registry.Password, err = decryptMaybe(r.encryptor, encryptedPassword.String) if err != nil { return nil, fmt.Errorf("failed to decrypt password: %w", err) } return registry, nil }