thread-master/apps/backend/internal/module/usage/domain/usage.go

126 lines
3.7 KiB
Go
Raw Normal View History

2026-07-13 01:15:30 +00:00
package domain
import (
"errors"
"time"
)
const (
KeyModePlatform = "platform"
KeyModeByok = "byok"
MeterAICopy = "ai_copy"
MeterAIResearch = "ai_research"
MeterWebSearch = "web_search"
MeterAIImage = "ai_image"
PlanFree = "free"
PlanStarter = "starter"
PlanPro = "pro"
)
var (
ErrNotFound = errors.New("usage not found")
ErrForbidden = errors.New("usage forbidden")
ErrNoKey = errors.New("no api key configured (platform or byok)")
ErrQuotaExceeded = errors.New("platform credits insufficient")
ErrInvalidPlan = errors.New("invalid plan")
)
type Event struct {
ID string `bson:"_id" json:"id"`
UID int64 `bson:"uid" json:"uid"`
Meter string `bson:"meter" json:"meter"`
Credits int `bson:"credits" json:"credits"` // platform only; byok always 0
KeyMode string `bson:"key_mode" json:"key_mode"`
Label string `bson:"label" json:"label"`
Source string `bson:"source" json:"source"`
MonthKey string `bson:"month_key" json:"month_key"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
}
type MemberPrefs struct {
UID int64 `bson:"uid" json:"uid"`
PlanID string `bson:"plan_id" json:"plan_id"`
Unlimited bool `bson:"unlimited" json:"unlimited"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type Purchase struct {
ID string `bson:"_id" json:"id"`
UID int64 `bson:"uid" json:"uid"`
PlanID string `bson:"plan_id" json:"plan_id"`
MockRef string `bson:"mock_ref,omitempty" json:"mock_ref,omitempty"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
}
type PlanDef struct {
ID string
MonthlyCredits int
}
var Plans = map[string]PlanDef{
PlanFree: {ID: PlanFree, MonthlyCredits: 80},
PlanStarter: {ID: PlanStarter, MonthlyCredits: 500},
PlanPro: {ID: PlanPro, MonthlyCredits: 2000},
}
var DefaultCreditCost = map[string]int{
MeterAICopy: 1, MeterAIResearch: 3, MeterWebSearch: 1, MeterAIImage: 5,
}
func NowNano() int64 { return time.Now().UTC().UnixNano() }
func MonthKey(nano int64) string {
t := time.Unix(0, nano).UTC()
return t.Format("2006-01")
}
func CurrentMonthKey() string { return MonthKey(NowNano()) }
// Summary split — never merge platform+byok into one total_calls only.
type MeterCount struct {
Count int `json:"count"`
Credits int `json:"credits"`
}
type PlatformBlock struct {
CreditsUsed int `json:"credits_used"`
CreditsRemaining int `json:"credits_remaining"`
CreditsTotal int `json:"credits_total"`
CallCount int `json:"call_count"`
ByMeter map[string]MeterCount `json:"by_meter"`
}
type ByokBlock struct {
CallCount int `json:"call_count"`
ByMeter map[string]MeterCount `json:"by_meter"`
}
type MonthSummary struct {
MonthKey string `json:"month_key"`
UID int64 `json:"uid"`
PlanID string `json:"plan_id"`
Unlimited bool `json:"unlimited"`
Platform PlatformBlock `json:"platform"`
Byok ByokBlock `json:"byok"`
// Legacy FE bars: only platform credits (not mixed)
TotalCredits int `json:"total_credits"`
RemainingCredits int `json:"remaining_credits"`
}
type TenantRow struct {
UID int64 `json:"uid"`
PlanID string `json:"plan_id"`
Unlimited bool `json:"unlimited"`
PlatformCreditsUsed int `json:"platform_credits_used"`
ByokCallCount int `json:"byok_call_count"`
}
type TenantSummary struct {
MonthKey string `json:"month_key"`
PlatformCreditsUsed int `json:"platform_credits_used"`
ByokCallCount int `json:"byok_call_count"`
Members []TenantRow `json:"members"`
}