Files
digital-psychology/apps/api/internal/repository/membership_plan_repo.go
T
jackyu66gitandCursor 882c01d81a feat(ECR-014): MembershipPlan 套餐配置并 Closed
membership_plans 表、admin 套餐页、Grant/CreateOrder 读表;
Loop continuous 自动 Approve/Closed。Next:ECR-015 RedemptionCode。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 18:03:38 +08:00

95 lines
2.5 KiB
Go

package repository
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// MembershipPlanRow is a configurable growth membership SKU.
type MembershipPlanRow struct {
Code string `json:"code"`
Title string `json:"title"`
DurationDays int `json:"duration_days"`
AmountCents int `json:"amount_cents"`
Active bool `json:"active"`
UpdatedAt time.Time `json:"updated_at"`
}
// ListMembershipPlans returns all plans ordered by code.
func (r *AdminRepo) ListMembershipPlans(ctx context.Context) ([]MembershipPlanRow, error) {
rows, err := r.Pool.Query(ctx, `
SELECT code, title, duration_days, amount_cents, active, updated_at
FROM membership_plans ORDER BY code`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []MembershipPlanRow
for rows.Next() {
var p MembershipPlanRow
if err := rows.Scan(&p.Code, &p.Title, &p.DurationDays, &p.AmountCents, &p.Active, &p.UpdatedAt); err != nil {
return nil, err
}
out = append(out, p)
}
return out, rows.Err()
}
// GetMembershipPlan loads one plan by code.
func (r *AdminRepo) GetMembershipPlan(ctx context.Context, code string) (*MembershipPlanRow, error) {
var p MembershipPlanRow
err := r.Pool.QueryRow(ctx, `
SELECT code, title, duration_days, amount_cents, active, updated_at
FROM membership_plans WHERE code=$1`, code,
).Scan(&p.Code, &p.Title, &p.DurationDays, &p.AmountCents, &p.Active, &p.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, err
}
return &p, nil
}
// UpdateMembershipPlanWithAudit updates mutable fields and audits.
func (r *AdminRepo) UpdateMembershipPlanWithAudit(
ctx context.Context,
adminID uuid.UUID,
code, title string,
days, amountCents int,
active bool,
meta json.RawMessage,
) error {
tx, err := r.Pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
tag, err := tx.Exec(ctx, `
UPDATE membership_plans
SET title=$2, duration_days=$3, amount_cents=$4, active=$5, updated_at=now()
WHERE code=$1`, code, title, days, amountCents, active)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return errString("plan not found")
}
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,'membership.plans.update','membership_plan',$2,$3)`,
adminID, code, meta,
); err != nil {
return err
}
return tx.Commit(ctx)
}