growth.write POST/PUT · migration 000056 · 无新 C 端 · Loop STOP Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
3.2 KiB
Go
117 lines
3.2 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
)
|
|
|
|
// FunnelDefinitionWriteInput is create/update payload.
|
|
type FunnelDefinitionWriteInput struct {
|
|
Code string
|
|
Title string
|
|
Active bool
|
|
}
|
|
|
|
// CreateFunnelDefinitionWithAudit inserts and audits.
|
|
func (r *AdminRepo) CreateFunnelDefinitionWithAudit(
|
|
ctx context.Context, adminID uuid.UUID, in FunnelDefinitionWriteInput, meta json.RawMessage,
|
|
) (*FunnelDefinitionRow, error) {
|
|
tx, err := r.Pool.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
var row FunnelDefinitionRow
|
|
err = tx.QueryRow(ctx, `
|
|
INSERT INTO funnel_definitions(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, mapFunnelDefinitionWriteErr(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,'growth.funnel_definition.create','funnel_definition',$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
|
|
}
|
|
|
|
// UpdateFunnelDefinitionWithAudit updates and audits.
|
|
func (r *AdminRepo) UpdateFunnelDefinitionWithAudit(
|
|
ctx context.Context, adminID, id uuid.UUID, in FunnelDefinitionWriteInput, meta json.RawMessage,
|
|
) (*FunnelDefinitionRow, 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 funnel_definitions 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 FunnelDefinitionRow
|
|
err = tx.QueryRow(ctx, `
|
|
UPDATE funnel_definitions
|
|
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, mapFunnelDefinitionWriteErr(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,'growth.funnel_definition.update','funnel_definition',$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 mapFunnelDefinitionWriteErr(err error) error {
|
|
var pgErr *pgconn.PgError
|
|
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
|
|
return errString("funnel definition code conflict")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// FunnelDefinitionCodeConflict reports unique violation.
|
|
func FunnelDefinitionCodeConflict(err error) bool {
|
|
return err != nil && strings.Contains(err.Error(), "funnel definition code conflict")
|
|
}
|