feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具
落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,8 +3,11 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/yuxingu/digital-psychology/apps/api/internal/model"
|
||||
@@ -38,6 +41,32 @@ func (r *ReportRepo) GetForUser(ctx context.Context, userID, reportID uuid.UUID)
|
||||
return rep, err
|
||||
}
|
||||
|
||||
// ListForUser returns recent reports for a user (summary only usage at service layer).
|
||||
func (r *ReportRepo) ListForUser(ctx context.Context, userID uuid.UUID, limit int) ([]model.GrowthReport, error) {
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
rows, err := r.Pool.Query(ctx, `
|
||||
SELECT id, user_id, profile_id, type, summary, detail, created_at
|
||||
FROM growth_reports
|
||||
WHERE user_id=$1 AND deleted_at IS NULL
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2`, userID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.GrowthReport
|
||||
for rows.Next() {
|
||||
var rep model.GrowthReport
|
||||
if err := rows.Scan(&rep.ID, &rep.UserID, &rep.ProfileID, &rep.Type, &rep.Summary, &rep.Detail, &rep.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, rep)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// HasDeepAccess reports whether user purchased deep access for report.
|
||||
func (r *ReportRepo) HasDeepAccess(ctx context.Context, userID, reportID uuid.UUID) (bool, error) {
|
||||
var ok bool
|
||||
@@ -60,6 +89,37 @@ func (r *ReportRepo) HasActiveMembership(ctx context.Context, userID uuid.UUID)
|
||||
return ok, err
|
||||
}
|
||||
|
||||
// MembershipRow is the current membership snapshot for a user.
|
||||
type MembershipRow struct {
|
||||
Plan string
|
||||
Status string
|
||||
ExpiresAt *time.Time
|
||||
AskQuotaLeft int
|
||||
Active bool
|
||||
}
|
||||
|
||||
// GetMembership returns membership status; missing row → inactive.
|
||||
func (r *ReportRepo) GetMembership(ctx context.Context, userID uuid.UUID) (*MembershipRow, error) {
|
||||
var plan, status string
|
||||
var expires *time.Time
|
||||
var quota int
|
||||
err := r.Pool.QueryRow(ctx, `
|
||||
SELECT plan, status, expires_at, ask_quota_left
|
||||
FROM memberships
|
||||
WHERE user_id=$1 AND deleted_at IS NULL`, userID,
|
||||
).Scan(&plan, &status, &expires, "a)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return &MembershipRow{Active: false, Status: "none"}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
active := status == "active" && expires != nil && expires.After(time.Now())
|
||||
return &MembershipRow{
|
||||
Plan: plan, Status: status, ExpiresAt: expires, AskQuotaLeft: quota, Active: active,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateOrder inserts an order.
|
||||
func (r *ReportRepo) CreateOrder(ctx context.Context, userID uuid.UUID, kind, plan string, reportID *uuid.UUID, amount int) (uuid.UUID, error) {
|
||||
var id uuid.UUID
|
||||
@@ -121,7 +181,7 @@ func (r *ReportRepo) PayMock(ctx context.Context, userID, orderID uuid.UUID) err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO memberships(user_id, plan, status, expires_at, ask_quota_left)
|
||||
VALUES ($1,$2,'active', now() + ($3::text || ' days')::interval, 100)
|
||||
VALUES ($1,$2,'active', now() + ($3 * interval '1 day'), 100)
|
||||
ON CONFLICT (user_id) DO UPDATE SET
|
||||
plan=EXCLUDED.plan, status='active',
|
||||
expires_at=EXCLUDED.expires_at, ask_quota_left=100, updated_at=now()`,
|
||||
|
||||
Reference in New Issue
Block a user