data layer

This commit is contained in:
2026-05-25 08:38:26 +07:00
parent 4e8c11d545
commit a428170fef
81 changed files with 3941 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
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
}