Commit 5906f9ab authored by yangjianbo's avatar yangjianbo
Browse files

fix(数据层): 修复数据完整性与仓储一致性问题

## 数据完整性修复 (fix-critical-data-integrity)
- 添加 error_translate.go 统一错误转换层
- 修复 nil 输入和 NotFound 错误处理
- 增强仓储层错误一致性

## 仓储一致性修复 (fix-high-repository-consistency)
- Group schema 添加 default_validity_days 字段
- Account schema 添加 proxy edge 关联
- 新增 UsageLog ent schema 定义
- 修复 UpdateBalance/UpdateConcurrency 受影响行数校验

## 数据卫生修复 (fix-medium-data-hygiene)
- UserSubscription 添加软删除支持 (SoftDeleteMixin)
- RedeemCode/Setting 添加硬删除策略文档
- account_groups/user_allowed_groups 的 created_at 声明 timestamptz
- 停止写入 legacy users.allowed_groups 列
- 新增迁移: 011-014 (索引优化、软删除、孤立数据审计、列清理)

## 测试补充
- 添加 UserSubscription 软删除测试
- 添加迁移回归测试
- 添加 NotFound 错误测试

🤖 Generated with [Claude Code](https://claude.com/claude-code

)
Co-Authored-By: default avatarClaude Opus 4.5 <noreply@anthropic.com>
parent 820bb16c
...@@ -8,10 +8,17 @@ import ( ...@@ -8,10 +8,17 @@ import (
"entgo.io/ent/dialect/entsql" "entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema" "entgo.io/ent/schema"
"entgo.io/ent/schema/field" "entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
) )
// Setting holds the schema definition for the Setting entity. // Setting holds the schema definition for the Setting entity.
//
// 删除策略:硬删除
// Setting 使用硬删除而非软删除,原因如下:
// - 系统设置是简单的键值对,删除即意味着恢复默认值
// - 设置变更通常通过应用日志追踪,无需在数据库层面保留历史
// - 保持表结构简洁,避免无效数据积累
//
// 如需设置变更审计,建议在更新/删除前将变更记录写入审计日志表。
type Setting struct { type Setting struct {
ent.Schema ent.Schema
} }
...@@ -43,7 +50,6 @@ func (Setting) Fields() []ent.Field { ...@@ -43,7 +50,6 @@ func (Setting) Fields() []ent.Field {
} }
func (Setting) Indexes() []ent.Index { func (Setting) Indexes() []ent.Index {
return []ent.Index{ // key 字段已在 Fields() 中声明 Unique(),无需额外索引
index.Fields("key").Unique(), return nil
}
} }
// Package schema 定义 Ent ORM 的数据库 schema。
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// UsageLog 定义使用日志实体的 schema。
//
// 使用日志记录每次 API 调用的详细信息,包括 token 使用量、成本计算等。
// 这是一个只追加的表,不支持更新和删除。
type UsageLog struct {
ent.Schema
}
// Annotations 返回 schema 的注解配置。
func (UsageLog) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "usage_logs"},
}
}
// Fields 定义使用日志实体的所有字段。
func (UsageLog) Fields() []ent.Field {
return []ent.Field{
// 关联字段
field.Int64("user_id"),
field.Int64("api_key_id"),
field.Int64("account_id"),
field.String("request_id").
MaxLen(64).
NotEmpty(),
field.String("model").
MaxLen(100).
NotEmpty(),
field.Int64("group_id").
Optional().
Nillable(),
field.Int64("subscription_id").
Optional().
Nillable(),
// Token 计数字段
field.Int("input_tokens").
Default(0),
field.Int("output_tokens").
Default(0),
field.Int("cache_creation_tokens").
Default(0),
field.Int("cache_read_tokens").
Default(0),
field.Int("cache_creation_5m_tokens").
Default(0),
field.Int("cache_creation_1h_tokens").
Default(0),
// 成本字段
field.Float("input_cost").
Default(0).
SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("output_cost").
Default(0).
SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("cache_creation_cost").
Default(0).
SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("cache_read_cost").
Default(0).
SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("total_cost").
Default(0).
SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("actual_cost").
Default(0).
SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}),
field.Float("rate_multiplier").
Default(1).
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}),
// 其他字段
field.Int8("billing_type").
Default(0),
field.Bool("stream").
Default(false),
field.Int("duration_ms").
Optional().
Nillable(),
field.Int("first_token_ms").
Optional().
Nillable(),
// 时间戳(只有 created_at,日志不可修改)
field.Time("created_at").
Default(time.Now).
Immutable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
// Edges 定义使用日志实体的关联关系。
func (UsageLog) Edges() []ent.Edge {
return []ent.Edge{
edge.From("user", User.Type).
Ref("usage_logs").
Field("user_id").
Required().
Unique(),
edge.From("api_key", ApiKey.Type).
Ref("usage_logs").
Field("api_key_id").
Required().
Unique(),
edge.From("account", Account.Type).
Ref("usage_logs").
Field("account_id").
Required().
Unique(),
edge.From("group", Group.Type).
Ref("usage_logs").
Field("group_id").
Unique(),
edge.From("subscription", UserSubscription.Type).
Ref("usage_logs").
Field("subscription_id").
Unique(),
}
}
// Indexes 定义数据库索引,优化查询性能。
func (UsageLog) Indexes() []ent.Index {
return []ent.Index{
index.Fields("user_id"),
index.Fields("api_key_id"),
index.Fields("account_id"),
index.Fields("group_id"),
index.Fields("subscription_id"),
index.Fields("created_at"),
index.Fields("model"),
index.Fields("request_id"),
// 复合索引用于时间范围查询
index.Fields("user_id", "created_at"),
index.Fields("api_key_id", "created_at"),
}
}
...@@ -73,12 +73,13 @@ func (User) Edges() []ent.Edge { ...@@ -73,12 +73,13 @@ func (User) Edges() []ent.Edge {
edge.To("assigned_subscriptions", UserSubscription.Type), edge.To("assigned_subscriptions", UserSubscription.Type),
edge.To("allowed_groups", Group.Type). edge.To("allowed_groups", Group.Type).
Through("user_allowed_groups", UserAllowedGroup.Type), Through("user_allowed_groups", UserAllowedGroup.Type),
edge.To("usage_logs", UsageLog.Type),
} }
} }
func (User) Indexes() []ent.Index { func (User) Indexes() []ent.Index {
return []ent.Index{ return []ent.Index{
index.Fields("email").Unique(), // email 字段已在 Fields() 中声明 Unique(),无需重复索引
index.Fields("status"), index.Fields("status"),
index.Fields("deleted_at"), index.Fields("deleted_at"),
} }
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"time" "time"
"entgo.io/ent" "entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql" "entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema" "entgo.io/ent/schema"
"entgo.io/ent/schema/edge" "entgo.io/ent/schema/edge"
...@@ -31,7 +32,8 @@ func (UserAllowedGroup) Fields() []ent.Field { ...@@ -31,7 +32,8 @@ func (UserAllowedGroup) Fields() []ent.Field {
field.Int64("group_id"), field.Int64("group_id"),
field.Time("created_at"). field.Time("created_at").
Immutable(). Immutable().
Default(time.Now), Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
} }
} }
......
...@@ -29,6 +29,7 @@ func (UserSubscription) Annotations() []schema.Annotation { ...@@ -29,6 +29,7 @@ func (UserSubscription) Annotations() []schema.Annotation {
func (UserSubscription) Mixin() []ent.Mixin { func (UserSubscription) Mixin() []ent.Mixin {
return []ent.Mixin{ return []ent.Mixin{
mixins.TimeMixin{}, mixins.TimeMixin{},
mixins.SoftDeleteMixin{},
} }
} }
...@@ -97,6 +98,7 @@ func (UserSubscription) Edges() []ent.Edge { ...@@ -97,6 +98,7 @@ func (UserSubscription) Edges() []ent.Edge {
Ref("assigned_subscriptions"). Ref("assigned_subscriptions").
Field("assigned_by"). Field("assigned_by").
Unique(), Unique(),
edge.To("usage_logs", UsageLog.Type),
} }
} }
...@@ -108,5 +110,6 @@ func (UserSubscription) Indexes() []ent.Index { ...@@ -108,5 +110,6 @@ func (UserSubscription) Indexes() []ent.Index {
index.Fields("expires_at"), index.Fields("expires_at"),
index.Fields("assigned_by"), index.Fields("assigned_by"),
index.Fields("user_id", "group_id").Unique(), index.Fields("user_id", "group_id").Unique(),
index.Fields("deleted_at"),
} }
} }
...@@ -28,6 +28,8 @@ type Tx struct { ...@@ -28,6 +28,8 @@ type Tx struct {
RedeemCode *RedeemCodeClient RedeemCode *RedeemCodeClient
// Setting is the client for interacting with the Setting builders. // Setting is the client for interacting with the Setting builders.
Setting *SettingClient Setting *SettingClient
// UsageLog is the client for interacting with the UsageLog builders.
UsageLog *UsageLogClient
// User is the client for interacting with the User builders. // User is the client for interacting with the User builders.
User *UserClient User *UserClient
// UserAllowedGroup is the client for interacting with the UserAllowedGroup builders. // UserAllowedGroup is the client for interacting with the UserAllowedGroup builders.
...@@ -172,6 +174,7 @@ func (tx *Tx) init() { ...@@ -172,6 +174,7 @@ func (tx *Tx) init() {
tx.Proxy = NewProxyClient(tx.config) tx.Proxy = NewProxyClient(tx.config)
tx.RedeemCode = NewRedeemCodeClient(tx.config) tx.RedeemCode = NewRedeemCodeClient(tx.config)
tx.Setting = NewSettingClient(tx.config) tx.Setting = NewSettingClient(tx.config)
tx.UsageLog = NewUsageLogClient(tx.config)
tx.User = NewUserClient(tx.config) tx.User = NewUserClient(tx.config)
tx.UserAllowedGroup = NewUserAllowedGroupClient(tx.config) tx.UserAllowedGroup = NewUserAllowedGroupClient(tx.config)
tx.UserSubscription = NewUserSubscriptionClient(tx.config) tx.UserSubscription = NewUserSubscriptionClient(tx.config)
...@@ -238,7 +241,6 @@ func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error ...@@ -238,7 +241,6 @@ func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error
var _ dialect.Driver = (*txDriver)(nil) var _ dialect.Driver = (*txDriver)(nil)
// ExecContext 透传到底层事务,用于在 ent 事务中执行原生 SQL(与 ent 写入保持同一事务)。
// ExecContext allows calling the underlying ExecContext method of the transaction if it is supported by it. // ExecContext allows calling the underlying ExecContext method of the transaction if it is supported by it.
// See, database/sql#Tx.ExecContext for more information. // See, database/sql#Tx.ExecContext for more information.
func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) { func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any) (stdsql.Result, error) {
...@@ -251,7 +253,6 @@ func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any) ...@@ -251,7 +253,6 @@ func (tx *txDriver) ExecContext(ctx context.Context, query string, args ...any)
return ex.ExecContext(ctx, query, args...) return ex.ExecContext(ctx, query, args...)
} }
// QueryContext 透传到底层事务,用于在 ent 事务中执行原生查询并共享锁语义。
// QueryContext allows calling the underlying QueryContext method of the transaction if it is supported by it. // QueryContext allows calling the underlying QueryContext method of the transaction if it is supported by it.
// See, database/sql#Tx.QueryContext for more information. // See, database/sql#Tx.QueryContext for more information.
func (tx *txDriver) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) { func (tx *txDriver) QueryContext(ctx context.Context, query string, args ...any) (*stdsql.Rows, error) {
......
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/account"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
)
// UsageLog is the model entity for the UsageLog schema.
type UsageLog struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// UserID holds the value of the "user_id" field.
UserID int64 `json:"user_id,omitempty"`
// APIKeyID holds the value of the "api_key_id" field.
APIKeyID int64 `json:"api_key_id,omitempty"`
// AccountID holds the value of the "account_id" field.
AccountID int64 `json:"account_id,omitempty"`
// RequestID holds the value of the "request_id" field.
RequestID string `json:"request_id,omitempty"`
// Model holds the value of the "model" field.
Model string `json:"model,omitempty"`
// GroupID holds the value of the "group_id" field.
GroupID *int64 `json:"group_id,omitempty"`
// SubscriptionID holds the value of the "subscription_id" field.
SubscriptionID *int64 `json:"subscription_id,omitempty"`
// InputTokens holds the value of the "input_tokens" field.
InputTokens int `json:"input_tokens,omitempty"`
// OutputTokens holds the value of the "output_tokens" field.
OutputTokens int `json:"output_tokens,omitempty"`
// CacheCreationTokens holds the value of the "cache_creation_tokens" field.
CacheCreationTokens int `json:"cache_creation_tokens,omitempty"`
// CacheReadTokens holds the value of the "cache_read_tokens" field.
CacheReadTokens int `json:"cache_read_tokens,omitempty"`
// CacheCreation5mTokens holds the value of the "cache_creation_5m_tokens" field.
CacheCreation5mTokens int `json:"cache_creation_5m_tokens,omitempty"`
// CacheCreation1hTokens holds the value of the "cache_creation_1h_tokens" field.
CacheCreation1hTokens int `json:"cache_creation_1h_tokens,omitempty"`
// InputCost holds the value of the "input_cost" field.
InputCost float64 `json:"input_cost,omitempty"`
// OutputCost holds the value of the "output_cost" field.
OutputCost float64 `json:"output_cost,omitempty"`
// CacheCreationCost holds the value of the "cache_creation_cost" field.
CacheCreationCost float64 `json:"cache_creation_cost,omitempty"`
// CacheReadCost holds the value of the "cache_read_cost" field.
CacheReadCost float64 `json:"cache_read_cost,omitempty"`
// TotalCost holds the value of the "total_cost" field.
TotalCost float64 `json:"total_cost,omitempty"`
// ActualCost holds the value of the "actual_cost" field.
ActualCost float64 `json:"actual_cost,omitempty"`
// RateMultiplier holds the value of the "rate_multiplier" field.
RateMultiplier float64 `json:"rate_multiplier,omitempty"`
// BillingType holds the value of the "billing_type" field.
BillingType int8 `json:"billing_type,omitempty"`
// Stream holds the value of the "stream" field.
Stream bool `json:"stream,omitempty"`
// DurationMs holds the value of the "duration_ms" field.
DurationMs *int `json:"duration_ms,omitempty"`
// FirstTokenMs holds the value of the "first_token_ms" field.
FirstTokenMs *int `json:"first_token_ms,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the UsageLogQuery when eager-loading is set.
Edges UsageLogEdges `json:"edges"`
selectValues sql.SelectValues
}
// UsageLogEdges holds the relations/edges for other nodes in the graph.
type UsageLogEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// APIKey holds the value of the api_key edge.
APIKey *ApiKey `json:"api_key,omitempty"`
// Account holds the value of the account edge.
Account *Account `json:"account,omitempty"`
// Group holds the value of the group edge.
Group *Group `json:"group,omitempty"`
// Subscription holds the value of the subscription edge.
Subscription *UserSubscription `json:"subscription,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [5]bool
}
// UserOrErr returns the User value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e UsageLogEdges) UserOrErr() (*User, error) {
if e.User != nil {
return e.User, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: user.Label}
}
return nil, &NotLoadedError{edge: "user"}
}
// APIKeyOrErr returns the APIKey value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e UsageLogEdges) APIKeyOrErr() (*ApiKey, error) {
if e.APIKey != nil {
return e.APIKey, nil
} else if e.loadedTypes[1] {
return nil, &NotFoundError{label: apikey.Label}
}
return nil, &NotLoadedError{edge: "api_key"}
}
// AccountOrErr returns the Account value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e UsageLogEdges) AccountOrErr() (*Account, error) {
if e.Account != nil {
return e.Account, nil
} else if e.loadedTypes[2] {
return nil, &NotFoundError{label: account.Label}
}
return nil, &NotLoadedError{edge: "account"}
}
// GroupOrErr returns the Group value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e UsageLogEdges) GroupOrErr() (*Group, error) {
if e.Group != nil {
return e.Group, nil
} else if e.loadedTypes[3] {
return nil, &NotFoundError{label: group.Label}
}
return nil, &NotLoadedError{edge: "group"}
}
// SubscriptionOrErr returns the Subscription value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e UsageLogEdges) SubscriptionOrErr() (*UserSubscription, error) {
if e.Subscription != nil {
return e.Subscription, nil
} else if e.loadedTypes[4] {
return nil, &NotFoundError{label: usersubscription.Label}
}
return nil, &NotLoadedError{edge: "subscription"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*UsageLog) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case usagelog.FieldStream:
values[i] = new(sql.NullBool)
case usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCacheCreationCost, usagelog.FieldCacheReadCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldRateMultiplier:
values[i] = new(sql.NullFloat64)
case usagelog.FieldID, usagelog.FieldUserID, usagelog.FieldAPIKeyID, usagelog.FieldAccountID, usagelog.FieldGroupID, usagelog.FieldSubscriptionID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheReadTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldBillingType, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs:
values[i] = new(sql.NullInt64)
case usagelog.FieldRequestID, usagelog.FieldModel:
values[i] = new(sql.NullString)
case usagelog.FieldCreatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the UsageLog fields.
func (_m *UsageLog) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case usagelog.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case usagelog.FieldUserID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field user_id", values[i])
} else if value.Valid {
_m.UserID = value.Int64
}
case usagelog.FieldAPIKeyID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field api_key_id", values[i])
} else if value.Valid {
_m.APIKeyID = value.Int64
}
case usagelog.FieldAccountID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field account_id", values[i])
} else if value.Valid {
_m.AccountID = value.Int64
}
case usagelog.FieldRequestID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field request_id", values[i])
} else if value.Valid {
_m.RequestID = value.String
}
case usagelog.FieldModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field model", values[i])
} else if value.Valid {
_m.Model = value.String
}
case usagelog.FieldGroupID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field group_id", values[i])
} else if value.Valid {
_m.GroupID = new(int64)
*_m.GroupID = value.Int64
}
case usagelog.FieldSubscriptionID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field subscription_id", values[i])
} else if value.Valid {
_m.SubscriptionID = new(int64)
*_m.SubscriptionID = value.Int64
}
case usagelog.FieldInputTokens:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field input_tokens", values[i])
} else if value.Valid {
_m.InputTokens = int(value.Int64)
}
case usagelog.FieldOutputTokens:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field output_tokens", values[i])
} else if value.Valid {
_m.OutputTokens = int(value.Int64)
}
case usagelog.FieldCacheCreationTokens:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field cache_creation_tokens", values[i])
} else if value.Valid {
_m.CacheCreationTokens = int(value.Int64)
}
case usagelog.FieldCacheReadTokens:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field cache_read_tokens", values[i])
} else if value.Valid {
_m.CacheReadTokens = int(value.Int64)
}
case usagelog.FieldCacheCreation5mTokens:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field cache_creation_5m_tokens", values[i])
} else if value.Valid {
_m.CacheCreation5mTokens = int(value.Int64)
}
case usagelog.FieldCacheCreation1hTokens:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field cache_creation_1h_tokens", values[i])
} else if value.Valid {
_m.CacheCreation1hTokens = int(value.Int64)
}
case usagelog.FieldInputCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field input_cost", values[i])
} else if value.Valid {
_m.InputCost = value.Float64
}
case usagelog.FieldOutputCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field output_cost", values[i])
} else if value.Valid {
_m.OutputCost = value.Float64
}
case usagelog.FieldCacheCreationCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field cache_creation_cost", values[i])
} else if value.Valid {
_m.CacheCreationCost = value.Float64
}
case usagelog.FieldCacheReadCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field cache_read_cost", values[i])
} else if value.Valid {
_m.CacheReadCost = value.Float64
}
case usagelog.FieldTotalCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field total_cost", values[i])
} else if value.Valid {
_m.TotalCost = value.Float64
}
case usagelog.FieldActualCost:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field actual_cost", values[i])
} else if value.Valid {
_m.ActualCost = value.Float64
}
case usagelog.FieldRateMultiplier:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_multiplier", values[i])
} else if value.Valid {
_m.RateMultiplier = value.Float64
}
case usagelog.FieldBillingType:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field billing_type", values[i])
} else if value.Valid {
_m.BillingType = int8(value.Int64)
}
case usagelog.FieldStream:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field stream", values[i])
} else if value.Valid {
_m.Stream = value.Bool
}
case usagelog.FieldDurationMs:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field duration_ms", values[i])
} else if value.Valid {
_m.DurationMs = new(int)
*_m.DurationMs = int(value.Int64)
}
case usagelog.FieldFirstTokenMs:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field first_token_ms", values[i])
} else if value.Valid {
_m.FirstTokenMs = new(int)
*_m.FirstTokenMs = int(value.Int64)
}
case usagelog.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the UsageLog.
// This includes values selected through modifiers, order, etc.
func (_m *UsageLog) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the UsageLog entity.
func (_m *UsageLog) QueryUser() *UserQuery {
return NewUsageLogClient(_m.config).QueryUser(_m)
}
// QueryAPIKey queries the "api_key" edge of the UsageLog entity.
func (_m *UsageLog) QueryAPIKey() *ApiKeyQuery {
return NewUsageLogClient(_m.config).QueryAPIKey(_m)
}
// QueryAccount queries the "account" edge of the UsageLog entity.
func (_m *UsageLog) QueryAccount() *AccountQuery {
return NewUsageLogClient(_m.config).QueryAccount(_m)
}
// QueryGroup queries the "group" edge of the UsageLog entity.
func (_m *UsageLog) QueryGroup() *GroupQuery {
return NewUsageLogClient(_m.config).QueryGroup(_m)
}
// QuerySubscription queries the "subscription" edge of the UsageLog entity.
func (_m *UsageLog) QuerySubscription() *UserSubscriptionQuery {
return NewUsageLogClient(_m.config).QuerySubscription(_m)
}
// Update returns a builder for updating this UsageLog.
// Note that you need to call UsageLog.Unwrap() before calling this method if this UsageLog
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *UsageLog) Update() *UsageLogUpdateOne {
return NewUsageLogClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the UsageLog entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *UsageLog) Unwrap() *UsageLog {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: UsageLog is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *UsageLog) String() string {
var builder strings.Builder
builder.WriteString("UsageLog(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
builder.WriteString(", ")
builder.WriteString("api_key_id=")
builder.WriteString(fmt.Sprintf("%v", _m.APIKeyID))
builder.WriteString(", ")
builder.WriteString("account_id=")
builder.WriteString(fmt.Sprintf("%v", _m.AccountID))
builder.WriteString(", ")
builder.WriteString("request_id=")
builder.WriteString(_m.RequestID)
builder.WriteString(", ")
builder.WriteString("model=")
builder.WriteString(_m.Model)
builder.WriteString(", ")
if v := _m.GroupID; v != nil {
builder.WriteString("group_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SubscriptionID; v != nil {
builder.WriteString("subscription_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("input_tokens=")
builder.WriteString(fmt.Sprintf("%v", _m.InputTokens))
builder.WriteString(", ")
builder.WriteString("output_tokens=")
builder.WriteString(fmt.Sprintf("%v", _m.OutputTokens))
builder.WriteString(", ")
builder.WriteString("cache_creation_tokens=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheCreationTokens))
builder.WriteString(", ")
builder.WriteString("cache_read_tokens=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheReadTokens))
builder.WriteString(", ")
builder.WriteString("cache_creation_5m_tokens=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheCreation5mTokens))
builder.WriteString(", ")
builder.WriteString("cache_creation_1h_tokens=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheCreation1hTokens))
builder.WriteString(", ")
builder.WriteString("input_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.InputCost))
builder.WriteString(", ")
builder.WriteString("output_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.OutputCost))
builder.WriteString(", ")
builder.WriteString("cache_creation_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheCreationCost))
builder.WriteString(", ")
builder.WriteString("cache_read_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.CacheReadCost))
builder.WriteString(", ")
builder.WriteString("total_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.TotalCost))
builder.WriteString(", ")
builder.WriteString("actual_cost=")
builder.WriteString(fmt.Sprintf("%v", _m.ActualCost))
builder.WriteString(", ")
builder.WriteString("rate_multiplier=")
builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
builder.WriteString(", ")
builder.WriteString("billing_type=")
builder.WriteString(fmt.Sprintf("%v", _m.BillingType))
builder.WriteString(", ")
builder.WriteString("stream=")
builder.WriteString(fmt.Sprintf("%v", _m.Stream))
builder.WriteString(", ")
if v := _m.DurationMs; v != nil {
builder.WriteString("duration_ms=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.FirstTokenMs; v != nil {
builder.WriteString("first_token_ms=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// UsageLogs is a parsable slice of UsageLog.
type UsageLogs []*UsageLog
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
)
// UsageLogDelete is the builder for deleting a UsageLog entity.
type UsageLogDelete struct {
config
hooks []Hook
mutation *UsageLogMutation
}
// Where appends a list predicates to the UsageLogDelete builder.
func (_d *UsageLogDelete) Where(ps ...predicate.UsageLog) *UsageLogDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *UsageLogDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *UsageLogDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *UsageLogDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(usagelog.Table, sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// UsageLogDeleteOne is the builder for deleting a single UsageLog entity.
type UsageLogDeleteOne struct {
_d *UsageLogDelete
}
// Where appends a list predicates to the UsageLogDelete builder.
func (_d *UsageLogDeleteOne) Where(ps ...predicate.UsageLog) *UsageLogDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *UsageLogDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{usagelog.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *UsageLogDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}
This diff is collapsed.
This diff is collapsed.
...@@ -59,11 +59,13 @@ type UserEdges struct { ...@@ -59,11 +59,13 @@ type UserEdges struct {
AssignedSubscriptions []*UserSubscription `json:"assigned_subscriptions,omitempty"` AssignedSubscriptions []*UserSubscription `json:"assigned_subscriptions,omitempty"`
// AllowedGroups holds the value of the allowed_groups edge. // AllowedGroups holds the value of the allowed_groups edge.
AllowedGroups []*Group `json:"allowed_groups,omitempty"` AllowedGroups []*Group `json:"allowed_groups,omitempty"`
// UsageLogs holds the value of the usage_logs edge.
UsageLogs []*UsageLog `json:"usage_logs,omitempty"`
// UserAllowedGroups holds the value of the user_allowed_groups edge. // UserAllowedGroups holds the value of the user_allowed_groups edge.
UserAllowedGroups []*UserAllowedGroup `json:"user_allowed_groups,omitempty"` UserAllowedGroups []*UserAllowedGroup `json:"user_allowed_groups,omitempty"`
// loadedTypes holds the information for reporting if a // loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not. // type was loaded (or requested) in eager-loading or not.
loadedTypes [6]bool loadedTypes [7]bool
} }
// APIKeysOrErr returns the APIKeys value or an error if the edge // APIKeysOrErr returns the APIKeys value or an error if the edge
...@@ -111,10 +113,19 @@ func (e UserEdges) AllowedGroupsOrErr() ([]*Group, error) { ...@@ -111,10 +113,19 @@ func (e UserEdges) AllowedGroupsOrErr() ([]*Group, error) {
return nil, &NotLoadedError{edge: "allowed_groups"} return nil, &NotLoadedError{edge: "allowed_groups"}
} }
// UsageLogsOrErr returns the UsageLogs value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) UsageLogsOrErr() ([]*UsageLog, error) {
if e.loadedTypes[5] {
return e.UsageLogs, nil
}
return nil, &NotLoadedError{edge: "usage_logs"}
}
// UserAllowedGroupsOrErr returns the UserAllowedGroups value or an error if the edge // UserAllowedGroupsOrErr returns the UserAllowedGroups value or an error if the edge
// was not loaded in eager-loading. // was not loaded in eager-loading.
func (e UserEdges) UserAllowedGroupsOrErr() ([]*UserAllowedGroup, error) { func (e UserEdges) UserAllowedGroupsOrErr() ([]*UserAllowedGroup, error) {
if e.loadedTypes[5] { if e.loadedTypes[6] {
return e.UserAllowedGroups, nil return e.UserAllowedGroups, nil
} }
return nil, &NotLoadedError{edge: "user_allowed_groups"} return nil, &NotLoadedError{edge: "user_allowed_groups"}
...@@ -265,6 +276,11 @@ func (_m *User) QueryAllowedGroups() *GroupQuery { ...@@ -265,6 +276,11 @@ func (_m *User) QueryAllowedGroups() *GroupQuery {
return NewUserClient(_m.config).QueryAllowedGroups(_m) return NewUserClient(_m.config).QueryAllowedGroups(_m)
} }
// QueryUsageLogs queries the "usage_logs" edge of the User entity.
func (_m *User) QueryUsageLogs() *UsageLogQuery {
return NewUserClient(_m.config).QueryUsageLogs(_m)
}
// QueryUserAllowedGroups queries the "user_allowed_groups" edge of the User entity. // QueryUserAllowedGroups queries the "user_allowed_groups" edge of the User entity.
func (_m *User) QueryUserAllowedGroups() *UserAllowedGroupQuery { func (_m *User) QueryUserAllowedGroups() *UserAllowedGroupQuery {
return NewUserClient(_m.config).QueryUserAllowedGroups(_m) return NewUserClient(_m.config).QueryUserAllowedGroups(_m)
......
...@@ -49,6 +49,8 @@ const ( ...@@ -49,6 +49,8 @@ const (
EdgeAssignedSubscriptions = "assigned_subscriptions" EdgeAssignedSubscriptions = "assigned_subscriptions"
// EdgeAllowedGroups holds the string denoting the allowed_groups edge name in mutations. // EdgeAllowedGroups holds the string denoting the allowed_groups edge name in mutations.
EdgeAllowedGroups = "allowed_groups" EdgeAllowedGroups = "allowed_groups"
// EdgeUsageLogs holds the string denoting the usage_logs edge name in mutations.
EdgeUsageLogs = "usage_logs"
// EdgeUserAllowedGroups holds the string denoting the user_allowed_groups edge name in mutations. // EdgeUserAllowedGroups holds the string denoting the user_allowed_groups edge name in mutations.
EdgeUserAllowedGroups = "user_allowed_groups" EdgeUserAllowedGroups = "user_allowed_groups"
// Table holds the table name of the user in the database. // Table holds the table name of the user in the database.
...@@ -86,6 +88,13 @@ const ( ...@@ -86,6 +88,13 @@ const (
// AllowedGroupsInverseTable is the table name for the Group entity. // AllowedGroupsInverseTable is the table name for the Group entity.
// It exists in this package in order to avoid circular dependency with the "group" package. // It exists in this package in order to avoid circular dependency with the "group" package.
AllowedGroupsInverseTable = "groups" AllowedGroupsInverseTable = "groups"
// UsageLogsTable is the table that holds the usage_logs relation/edge.
UsageLogsTable = "usage_logs"
// UsageLogsInverseTable is the table name for the UsageLog entity.
// It exists in this package in order to avoid circular dependency with the "usagelog" package.
UsageLogsInverseTable = "usage_logs"
// UsageLogsColumn is the table column denoting the usage_logs relation/edge.
UsageLogsColumn = "user_id"
// UserAllowedGroupsTable is the table that holds the user_allowed_groups relation/edge. // UserAllowedGroupsTable is the table that holds the user_allowed_groups relation/edge.
UserAllowedGroupsTable = "user_allowed_groups" UserAllowedGroupsTable = "user_allowed_groups"
// UserAllowedGroupsInverseTable is the table name for the UserAllowedGroup entity. // UserAllowedGroupsInverseTable is the table name for the UserAllowedGroup entity.
...@@ -308,6 +317,20 @@ func ByAllowedGroups(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { ...@@ -308,6 +317,20 @@ func ByAllowedGroups(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
} }
} }
// ByUsageLogsCount orders the results by usage_logs count.
func ByUsageLogsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newUsageLogsStep(), opts...)
}
}
// ByUsageLogs orders the results by usage_logs terms.
func ByUsageLogs(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUsageLogsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByUserAllowedGroupsCount orders the results by user_allowed_groups count. // ByUserAllowedGroupsCount orders the results by user_allowed_groups count.
func ByUserAllowedGroupsCount(opts ...sql.OrderTermOption) OrderOption { func ByUserAllowedGroupsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) { return func(s *sql.Selector) {
...@@ -356,6 +379,13 @@ func newAllowedGroupsStep() *sqlgraph.Step { ...@@ -356,6 +379,13 @@ func newAllowedGroupsStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.M2M, false, AllowedGroupsTable, AllowedGroupsPrimaryKey...), sqlgraph.Edge(sqlgraph.M2M, false, AllowedGroupsTable, AllowedGroupsPrimaryKey...),
) )
} }
func newUsageLogsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UsageLogsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, UsageLogsTable, UsageLogsColumn),
)
}
func newUserAllowedGroupsStep() *sqlgraph.Step { func newUserAllowedGroupsStep() *sqlgraph.Step {
return sqlgraph.NewStep( return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID), sqlgraph.From(Table, FieldID),
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment