落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
2.0 KiB
Go
78 lines
2.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
const imageCardDailyFree = 3
|
|
|
|
// ImageCardRepo tracks daily draw quotas.
|
|
type ImageCardRepo struct {
|
|
Pool *pgxpool.Pool
|
|
}
|
|
|
|
// UsedToday returns how many free draws used today.
|
|
func (r *ImageCardRepo) UsedToday(ctx context.Context, userID uuid.UUID, day time.Time) (int, error) {
|
|
var n int
|
|
err := r.Pool.QueryRow(ctx, `
|
|
SELECT used FROM image_card_quotas
|
|
WHERE user_id=$1 AND day=$2::date`,
|
|
userID, day.Format("2006-01-02"),
|
|
).Scan(&n)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return 0, nil
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// TryConsume increments today's used count when under free limit.
|
|
// Returns remaining after consume. ErrQuotaExhausted when free tier is out.
|
|
func (r *ImageCardRepo) TryConsume(ctx context.Context, userID uuid.UUID, day time.Time) (remaining int, err error) {
|
|
dayStr := day.Format("2006-01-02")
|
|
var used int
|
|
err = r.Pool.QueryRow(ctx, `
|
|
INSERT INTO image_card_quotas(user_id, day, used)
|
|
VALUES ($1, $2::date, 1)
|
|
ON CONFLICT (user_id, day) DO UPDATE
|
|
SET used = image_card_quotas.used + 1
|
|
WHERE image_card_quotas.used < $3
|
|
RETURNING used`,
|
|
userID, dayStr, imageCardDailyFree,
|
|
).Scan(&used)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return 0, ErrQuotaExhausted
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return imageCardDailyFree - used, nil
|
|
}
|
|
|
|
// RemainingToday without consuming.
|
|
func (r *ImageCardRepo) RemainingToday(ctx context.Context, userID uuid.UUID, day time.Time, unlimited bool) (int, error) {
|
|
if unlimited {
|
|
return 99, nil
|
|
}
|
|
used, err := r.UsedToday(ctx, userID, day)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
left := imageCardDailyFree - used
|
|
if left < 0 {
|
|
left = 0
|
|
}
|
|
return left, nil
|
|
}
|
|
|
|
// DailyFreeLimit is the free-tier cap.
|
|
func DailyFreeLimit() int { return imageCardDailyFree }
|
|
|
|
// ErrQuotaExhausted means free daily draws are used up.
|
|
var ErrQuotaExhausted = errors.New("今日免费次数已用完")
|