package backend import ( "context" "database/sql" "errors" "fmt" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/model" "github.com/google/uuid" ) var ErrNotFound = errors.New("backend not found") const backendRows = `"id", "name", "oidc_issuer", "oidc_client_id", "oidc_client_secret", "oidc_redirect_uri"` type scannable interface { Scan(dest ...any) error } type BackendDB interface { GetAllBackends(ctx context.Context) ([]*model.Backend, error) GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) GetBackendByName(ctx context.Context, name string) (*model.Backend, error) AddBackend(ctx context.Context, newBackend *model.Backend) error DeleteBackend(ctx context.Context, id uuid.UUID) error } type sqlBackendDB struct { db *sql.DB } func backendFromRow(row scannable) (*model.Backend, error) { var res model.Backend if err := row.Scan(&res.ID, &res.Name, &res.OIDCConfig.Issuer, &res.OIDCConfig.ClientID, &res.OIDCConfig.ClientSecret, &res.OIDCConfig.RedirectURI); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } return nil, fmt.Errorf("invalid format for backend: %w", err) } return &res, nil } func (db *sqlBackendDB) GetBackendByName(ctx context.Context, name string) (*model.Backend, error) { query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "name" = ?`, backendRows) row := db.db.QueryRowContext(ctx, query, name) return backendFromRow(row) } func (db *sqlBackendDB) GetBackendByID(ctx context.Context, id uuid.UUID) (*model.Backend, error) { query := fmt.Sprintf(`SELECT %s FROM "backend" WHERE "id" = ?`, backendRows) row := db.db.QueryRowContext(ctx, query, id) return backendFromRow(row) } func (db *sqlBackendDB) GetAllBackends(ctx context.Context) ([]*model.Backend, error) { rows, err := db.db.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM "backend"`, backendRows)) if err != nil { return nil, err } var res []*model.Backend for rows.Next() { b, err := backendFromRow(rows) if err != nil { return nil, err } res = append(res, b) } return res, nil } func (db *sqlBackendDB) AddBackend(ctx context.Context, newBackend *model.Backend) error { tx, err := db.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("failed to start transaction: %w", err) } defer func() { _ = tx.Rollback() }() query := fmt.Sprintf(`INSERT INTO "backend" (%s) VALUES ($1, $2, $3, $4, $5, $6)`, backendRows) _, err = tx.ExecContext( ctx, query, newBackend.ID, newBackend.Name, newBackend.OIDCConfig.Issuer, newBackend.OIDCConfig.ClientID, newBackend.OIDCConfig.ClientSecret, newBackend.OIDCConfig.RedirectURI, ) if err != nil { return fmt.Errorf("failed to insert in DB: %w", err) } if err := tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %w", err) } return nil } func (db *sqlBackendDB) DeleteBackend(ctx context.Context, id uuid.UUID) error { tx, err := db.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("failed to start transaction: %w", err) } defer func() { _ = tx.Rollback() }() if _, err := tx.ExecContext(ctx, `DELETE FROM "backend" WHERE id = $1`, id.String()); err != nil { return fmt.Errorf("failed to run query: %w", err) } if err := tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %w", err) } return nil } func New(db *sql.DB) *sqlBackendDB { return &sqlBackendDB{db: db} }