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:
@@ -0,0 +1,131 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
)
|
||||
|
||||
// GrowthRepo persists growth plans and check-ins.
|
||||
type GrowthRepo struct {
|
||||
Pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
// CreatePlan inserts a plan.
|
||||
func (r *GrowthRepo) CreatePlan(ctx context.Context, userID uuid.UUID, title, focus string) (*model.GrowthPlan, error) {
|
||||
p := &model.GrowthPlan{}
|
||||
err := r.Pool.QueryRow(ctx, `
|
||||
INSERT INTO growth_plans(user_id, title, focus)
|
||||
VALUES ($1,$2,$3)
|
||||
RETURNING id, user_id, title, focus, status, created_at`,
|
||||
userID, title, focus,
|
||||
).Scan(&p.ID, &p.UserID, &p.Title, &p.Focus, &p.Status, &p.CreatedAt)
|
||||
return p, err
|
||||
}
|
||||
|
||||
// ListPlans returns active plans for user.
|
||||
func (r *GrowthRepo) ListPlans(ctx context.Context, userID uuid.UUID) ([]model.GrowthPlan, error) {
|
||||
rows, err := r.Pool.Query(ctx, `
|
||||
SELECT id, user_id, title, focus, status, created_at
|
||||
FROM growth_plans
|
||||
WHERE user_id=$1 AND deleted_at IS NULL AND status='active'
|
||||
ORDER BY created_at DESC LIMIT 20`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.GrowthPlan
|
||||
for rows.Next() {
|
||||
var p model.GrowthPlan
|
||||
if err := rows.Scan(&p.ID, &p.UserID, &p.Title, &p.Focus, &p.Status, &p.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// Checkin upserts today's check-in.
|
||||
func (r *GrowthRepo) Checkin(ctx context.Context, userID, planID uuid.UUID, day time.Time, note *string) (*model.GrowthCheckin, error) {
|
||||
var owner uuid.UUID
|
||||
err := r.Pool.QueryRow(ctx, `
|
||||
SELECT user_id FROM growth_plans WHERE id=$1 AND deleted_at IS NULL`, planID,
|
||||
).Scan(&owner)
|
||||
if err != nil {
|
||||
return nil, errors.New("plan not found")
|
||||
}
|
||||
if owner != userID {
|
||||
return nil, errors.New("plan not found")
|
||||
}
|
||||
c := &model.GrowthCheckin{}
|
||||
var dayOut time.Time
|
||||
err = r.Pool.QueryRow(ctx, `
|
||||
INSERT INTO growth_checkins(plan_id, user_id, day, note)
|
||||
VALUES ($1,$2,$3::date,$4)
|
||||
ON CONFLICT (plan_id, day) DO UPDATE SET note=EXCLUDED.note
|
||||
RETURNING id, plan_id, user_id, day, note, created_at`,
|
||||
planID, userID, day.Format("2006-01-02"), note,
|
||||
).Scan(&c.ID, &c.PlanID, &c.UserID, &dayOut, &c.Note, &c.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Day = dayOut.Format("2006-01-02")
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ListCheckinsRecent returns check-ins for a plan in last N days.
|
||||
func (r *GrowthRepo) ListCheckinsRecent(ctx context.Context, userID, planID uuid.UUID, days int) ([]model.GrowthCheckin, error) {
|
||||
rows, err := r.Pool.Query(ctx, `
|
||||
SELECT id, plan_id, user_id, day, note, created_at
|
||||
FROM growth_checkins
|
||||
WHERE user_id=$1 AND plan_id=$2 AND day >= (CURRENT_DATE - $3::int)
|
||||
ORDER BY day DESC`, userID, planID, days)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.GrowthCheckin
|
||||
for rows.Next() {
|
||||
var c model.GrowthCheckin
|
||||
var dayOut time.Time
|
||||
if err := rows.Scan(&c.ID, &c.PlanID, &c.UserID, &dayOut, &c.Note, &c.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Day = dayOut.Format("2006-01-02")
|
||||
out = append(out, c)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ListMoodsRecent for companion trail (reuse moods table).
|
||||
func (r *GrowthRepo) ListMoodsRecent(ctx context.Context, userID uuid.UUID, days int) ([]model.Mood, error) {
|
||||
rows, err := r.Pool.Query(ctx, `
|
||||
SELECT id, user_id, day, score, note, created_at
|
||||
FROM moods
|
||||
WHERE user_id=$1 AND deleted_at IS NULL AND day >= (CURRENT_DATE - $2::int)
|
||||
ORDER BY day DESC`, userID, days)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []model.Mood
|
||||
for rows.Next() {
|
||||
var m model.Mood
|
||||
var dayOut time.Time
|
||||
if err := rows.Scan(&m.ID, &m.UserID, &dayOut, &m.Score, &m.Note, &m.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Day = dayOut.Format("2006-01-02")
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ErrNoRows re-export helper.
|
||||
var ErrNoRows = pgx.ErrNoRows
|
||||
Reference in New Issue
Block a user