121 lines
3.6 KiB
Go
121 lines
3.6 KiB
Go
package repo
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/dbiz/cdp/data-layer/api/internal/apperr"
|
|
"github.com/dbiz/cdp/data-layer/api/internal/model"
|
|
)
|
|
|
|
type SavedQueryRepo struct {
|
|
pg *pgxpool.Pool
|
|
}
|
|
|
|
func NewSavedQueryRepo(pg *pgxpool.Pool) *SavedQueryRepo { return &SavedQueryRepo{pg: pg} }
|
|
|
|
const (
|
|
insertSavedQuery = `
|
|
INSERT INTO saved_queries (workspace_id, owner_id, name, kind, spec)
|
|
VALUES ($1, NULLIF($2, '')::uuid, $3, $4, $5)
|
|
RETURNING id, workspace_id, COALESCE(owner_id::text, '') AS owner_id, name, kind, spec, created_at, updated_at
|
|
`
|
|
selectSavedQueries = `
|
|
SELECT id, workspace_id, COALESCE(owner_id::text, '') AS owner_id, name, kind, spec, created_at, updated_at
|
|
FROM saved_queries
|
|
WHERE workspace_id = $1
|
|
ORDER BY updated_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
selectSavedQuery = `
|
|
SELECT id, workspace_id, COALESCE(owner_id::text, '') AS owner_id, name, kind, spec, created_at, updated_at
|
|
FROM saved_queries
|
|
WHERE workspace_id = $1 AND id = $2
|
|
`
|
|
updateSavedQuery = `
|
|
UPDATE saved_queries
|
|
SET name = $3, spec = $4, updated_at = now()
|
|
WHERE workspace_id = $1 AND id = $2
|
|
RETURNING id, workspace_id, COALESCE(owner_id::text, '') AS owner_id, name, kind, spec, created_at, updated_at
|
|
`
|
|
deleteSavedQuery = `DELETE FROM saved_queries WHERE workspace_id = $1 AND id = $2`
|
|
)
|
|
|
|
func (r *SavedQueryRepo) Create(ctx context.Context, q model.SavedQuery) (*model.SavedQuery, error) {
|
|
spec, err := json.Marshal(q.Spec)
|
|
if err != nil {
|
|
return nil, apperr.BadRequest("spec must be valid json", "spec", err)
|
|
}
|
|
row := r.pg.QueryRow(ctx, insertSavedQuery, q.WorkspaceID, q.OwnerID, q.Name, q.Kind, spec)
|
|
return scanSavedQuery(row)
|
|
}
|
|
|
|
func (r *SavedQueryRepo) List(ctx context.Context, workspaceID string, limit, offset int) ([]model.SavedQuery, error) {
|
|
rows, err := r.pg.Query(ctx, selectSavedQueries, workspaceID, limit, offset)
|
|
if err != nil {
|
|
return nil, apperr.Internal(err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := []model.SavedQuery{}
|
|
for rows.Next() {
|
|
q, err := scanSavedQuery(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, *q)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *SavedQueryRepo) Get(ctx context.Context, workspaceID, id string) (*model.SavedQuery, error) {
|
|
row := r.pg.QueryRow(ctx, selectSavedQuery, workspaceID, id)
|
|
return scanSavedQuery(row)
|
|
}
|
|
|
|
func (r *SavedQueryRepo) Update(ctx context.Context, workspaceID, id, name string, spec map[string]any) (*model.SavedQuery, error) {
|
|
specJSON, err := json.Marshal(spec)
|
|
if err != nil {
|
|
return nil, apperr.BadRequest("spec must be valid json", "spec", err)
|
|
}
|
|
row := r.pg.QueryRow(ctx, updateSavedQuery, workspaceID, id, name, specJSON)
|
|
return scanSavedQuery(row)
|
|
}
|
|
|
|
func (r *SavedQueryRepo) Delete(ctx context.Context, workspaceID, id string) error {
|
|
ct, err := r.pg.Exec(ctx, deleteSavedQuery, workspaceID, id)
|
|
if err != nil {
|
|
return apperr.Internal(err)
|
|
}
|
|
if ct.RowsAffected() == 0 {
|
|
return apperr.NotFound("saved query not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// scanSavedQuery accepts both pgx.Row and pgx.Rows (they share Scan).
|
|
type scanner interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func scanSavedQuery(s scanner) (*model.SavedQuery, error) {
|
|
var q model.SavedQuery
|
|
var specRaw []byte
|
|
if err := s.Scan(&q.ID, &q.WorkspaceID, &q.OwnerID, &q.Name, &q.Kind, &specRaw, &q.CreatedAt, &q.UpdatedAt); err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, apperr.NotFound("saved query not found")
|
|
}
|
|
return nil, apperr.Internal(err)
|
|
}
|
|
if len(specRaw) > 0 {
|
|
if err := json.Unmarshal(specRaw, &q.Spec); err != nil {
|
|
return nil, apperr.Internal(err)
|
|
}
|
|
}
|
|
return &q, nil
|
|
}
|