package repository import ( "context" "encoding/json" "errors" "strings" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" ) // RhythmConfigWriteInput is create/update payload. type RhythmConfigWriteInput struct { Code string Title string Active bool } // ListActiveRhythmConfigs returns active configs for C-end. func (r *AdminRepo) ListActiveRhythmConfigs(ctx context.Context) ([]RhythmConfigRow, error) { rows, err := r.Pool.Query(ctx, ` SELECT id, code, title, active, system, updated_at FROM rhythm_configs WHERE active = true ORDER BY code ASC LIMIT 100`) if err != nil { return nil, err } defer rows.Close() var out []RhythmConfigRow for rows.Next() { var row RhythmConfigRow if err := rows.Scan(&row.ID, &row.Code, &row.Title, &row.Active, &row.System, &row.UpdatedAt); err != nil { return nil, err } out = append(out, row) } return out, rows.Err() } // CreateRhythmConfigWithAudit inserts and audits. func (r *AdminRepo) CreateRhythmConfigWithAudit( ctx context.Context, adminID uuid.UUID, in RhythmConfigWriteInput, meta json.RawMessage, ) (*RhythmConfigRow, error) { tx, err := r.Pool.Begin(ctx) if err != nil { return nil, err } defer tx.Rollback(ctx) var row RhythmConfigRow err = tx.QueryRow(ctx, ` INSERT INTO rhythm_configs(code, title, active, system) VALUES ($1,$2,$3,false) RETURNING id, code, title, active, system, updated_at`, in.Code, in.Title, in.Active, ).Scan(&row.ID, &row.Code, &row.Title, &row.Active, &row.System, &row.UpdatedAt) if err != nil { return nil, mapRhythmConfigWriteErr(err) } if meta == nil { meta = json.RawMessage(`{}`) } if _, err := tx.Exec(ctx, ` INSERT INTO admin_audit_logs(admin_id, action, target_type, target_id, meta) VALUES ($1,'explore.rhythm_config.create','rhythm_config',$2,$3)`, adminID, row.ID.String(), meta, ); err != nil { return nil, err } if err := tx.Commit(ctx); err != nil { return nil, err } return &row, nil } // UpdateRhythmConfigWithAudit updates and audits. func (r *AdminRepo) UpdateRhythmConfigWithAudit( ctx context.Context, adminID, id uuid.UUID, in RhythmConfigWriteInput, meta json.RawMessage, ) (*RhythmConfigRow, error) { tx, err := r.Pool.Begin(ctx) if err != nil { return nil, err } defer tx.Rollback(ctx) var system bool var oldCode string err = tx.QueryRow(ctx, `SELECT system, code FROM rhythm_configs WHERE id=$1`, id).Scan(&system, &oldCode) if errors.Is(err, pgx.ErrNoRows) { return nil, pgx.ErrNoRows } if err != nil { return nil, err } code := in.Code if system { code = oldCode } var row RhythmConfigRow err = tx.QueryRow(ctx, ` UPDATE rhythm_configs SET code=$2, title=$3, active=$4, updated_at=now() WHERE id=$1 RETURNING id, code, title, active, system, updated_at`, id, code, in.Title, in.Active, ).Scan(&row.ID, &row.Code, &row.Title, &row.Active, &row.System, &row.UpdatedAt) if err != nil { return nil, mapRhythmConfigWriteErr(err) } if meta == nil { meta = json.RawMessage(`{}`) } if _, err := tx.Exec(ctx, ` INSERT INTO admin_audit_logs(admin_id, action, target_type, target_id, meta) VALUES ($1,'explore.rhythm_config.update','rhythm_config',$2,$3)`, adminID, id.String(), meta, ); err != nil { return nil, err } if err := tx.Commit(ctx); err != nil { return nil, err } return &row, nil } func mapRhythmConfigWriteErr(err error) error { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return errString("rhythm config code conflict") } return err } // RhythmConfigCodeConflict reports unique violation. func RhythmConfigCodeConflict(err error) bool { return err != nil && strings.Contains(err.Error(), "rhythm config code conflict") }