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
......@@ -18,6 +18,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
......@@ -33,6 +34,7 @@ type GroupQuery struct {
withAPIKeys *ApiKeyQuery
withRedeemCodes *RedeemCodeQuery
withSubscriptions *UserSubscriptionQuery
withUsageLogs *UsageLogQuery
withAccounts *AccountQuery
withAllowedUsers *UserQuery
withAccountGroups *AccountGroupQuery
......@@ -139,6 +141,28 @@ func (_q *GroupQuery) QuerySubscriptions() *UserSubscriptionQuery {
return query
}
// QueryUsageLogs chains the current query on the "usage_logs" edge.
func (_q *GroupQuery) QueryUsageLogs() *UsageLogQuery {
query := (&UsageLogClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(group.Table, group.FieldID, selector),
sqlgraph.To(usagelog.Table, usagelog.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, group.UsageLogsTable, group.UsageLogsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryAccounts chains the current query on the "accounts" edge.
func (_q *GroupQuery) QueryAccounts() *AccountQuery {
query := (&AccountClient{config: _q.config}).Query()
......@@ -422,6 +446,7 @@ func (_q *GroupQuery) Clone() *GroupQuery {
withAPIKeys: _q.withAPIKeys.Clone(),
withRedeemCodes: _q.withRedeemCodes.Clone(),
withSubscriptions: _q.withSubscriptions.Clone(),
withUsageLogs: _q.withUsageLogs.Clone(),
withAccounts: _q.withAccounts.Clone(),
withAllowedUsers: _q.withAllowedUsers.Clone(),
withAccountGroups: _q.withAccountGroups.Clone(),
......@@ -465,6 +490,17 @@ func (_q *GroupQuery) WithSubscriptions(opts ...func(*UserSubscriptionQuery)) *G
return _q
}
// WithUsageLogs tells the query-builder to eager-load the nodes that are connected to
// the "usage_logs" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *GroupQuery) WithUsageLogs(opts ...func(*UsageLogQuery)) *GroupQuery {
query := (&UsageLogClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withUsageLogs = query
return _q
}
// WithAccounts tells the query-builder to eager-load the nodes that are connected to
// the "accounts" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *GroupQuery) WithAccounts(opts ...func(*AccountQuery)) *GroupQuery {
......@@ -587,10 +623,11 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
var (
nodes = []*Group{}
_spec = _q.querySpec()
loadedTypes = [7]bool{
loadedTypes = [8]bool{
_q.withAPIKeys != nil,
_q.withRedeemCodes != nil,
_q.withSubscriptions != nil,
_q.withUsageLogs != nil,
_q.withAccounts != nil,
_q.withAllowedUsers != nil,
_q.withAccountGroups != nil,
......@@ -636,6 +673,13 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
return nil, err
}
}
if query := _q.withUsageLogs; query != nil {
if err := _q.loadUsageLogs(ctx, query, nodes,
func(n *Group) { n.Edges.UsageLogs = []*UsageLog{} },
func(n *Group, e *UsageLog) { n.Edges.UsageLogs = append(n.Edges.UsageLogs, e) }); err != nil {
return nil, err
}
}
if query := _q.withAccounts; query != nil {
if err := _q.loadAccounts(ctx, query, nodes,
func(n *Group) { n.Edges.Accounts = []*Account{} },
......@@ -763,6 +807,39 @@ func (_q *GroupQuery) loadSubscriptions(ctx context.Context, query *UserSubscrip
}
return nil
}
func (_q *GroupQuery) loadUsageLogs(ctx context.Context, query *UsageLogQuery, nodes []*Group, init func(*Group), assign func(*Group, *UsageLog)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*Group)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(usagelog.FieldGroupID)
}
query.Where(predicate.UsageLog(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(group.UsageLogsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.GroupID
if fk == nil {
return fmt.Errorf(`foreign-key "group_id" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "group_id" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *GroupQuery) loadAccounts(ctx context.Context, query *AccountQuery, nodes []*Group, init func(*Group), assign func(*Group, *Account)) error {
edgeIDs := make([]driver.Value, len(nodes))
byID := make(map[int64]*Group)
......
......@@ -16,6 +16,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
)
......@@ -251,6 +252,27 @@ func (_u *GroupUpdate) ClearMonthlyLimitUsd() *GroupUpdate {
return _u
}
// SetDefaultValidityDays sets the "default_validity_days" field.
func (_u *GroupUpdate) SetDefaultValidityDays(v int) *GroupUpdate {
_u.mutation.ResetDefaultValidityDays()
_u.mutation.SetDefaultValidityDays(v)
return _u
}
// SetNillableDefaultValidityDays sets the "default_validity_days" field if the given value is not nil.
func (_u *GroupUpdate) SetNillableDefaultValidityDays(v *int) *GroupUpdate {
if v != nil {
_u.SetDefaultValidityDays(*v)
}
return _u
}
// AddDefaultValidityDays adds value to the "default_validity_days" field.
func (_u *GroupUpdate) AddDefaultValidityDays(v int) *GroupUpdate {
_u.mutation.AddDefaultValidityDays(v)
return _u
}
// AddAPIKeyIDs adds the "api_keys" edge to the ApiKey entity by IDs.
func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate {
_u.mutation.AddAPIKeyIDs(ids...)
......@@ -296,6 +318,21 @@ func (_u *GroupUpdate) AddSubscriptions(v ...*UserSubscription) *GroupUpdate {
return _u.AddSubscriptionIDs(ids...)
}
// AddUsageLogIDs adds the "usage_logs" edge to the UsageLog entity by IDs.
func (_u *GroupUpdate) AddUsageLogIDs(ids ...int64) *GroupUpdate {
_u.mutation.AddUsageLogIDs(ids...)
return _u
}
// AddUsageLogs adds the "usage_logs" edges to the UsageLog entity.
func (_u *GroupUpdate) AddUsageLogs(v ...*UsageLog) *GroupUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddUsageLogIDs(ids...)
}
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
func (_u *GroupUpdate) AddAccountIDs(ids ...int64) *GroupUpdate {
_u.mutation.AddAccountIDs(ids...)
......@@ -394,6 +431,27 @@ func (_u *GroupUpdate) RemoveSubscriptions(v ...*UserSubscription) *GroupUpdate
return _u.RemoveSubscriptionIDs(ids...)
}
// ClearUsageLogs clears all "usage_logs" edges to the UsageLog entity.
func (_u *GroupUpdate) ClearUsageLogs() *GroupUpdate {
_u.mutation.ClearUsageLogs()
return _u
}
// RemoveUsageLogIDs removes the "usage_logs" edge to UsageLog entities by IDs.
func (_u *GroupUpdate) RemoveUsageLogIDs(ids ...int64) *GroupUpdate {
_u.mutation.RemoveUsageLogIDs(ids...)
return _u
}
// RemoveUsageLogs removes "usage_logs" edges to UsageLog entities.
func (_u *GroupUpdate) RemoveUsageLogs(v ...*UsageLog) *GroupUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveUsageLogIDs(ids...)
}
// ClearAccounts clears all "accounts" edges to the Account entity.
func (_u *GroupUpdate) ClearAccounts() *GroupUpdate {
_u.mutation.ClearAccounts()
......@@ -578,6 +636,12 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.MonthlyLimitUsdCleared() {
_spec.ClearField(group.FieldMonthlyLimitUsd, field.TypeFloat64)
}
if value, ok := _u.mutation.DefaultValidityDays(); ok {
_spec.SetField(group.FieldDefaultValidityDays, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedDefaultValidityDays(); ok {
_spec.AddField(group.FieldDefaultValidityDays, field.TypeInt, value)
}
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
......@@ -713,6 +777,51 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UsageLogsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.UsageLogsTable,
Columns: []string{group.UsageLogsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedUsageLogsIDs(); len(nodes) > 0 && !_u.mutation.UsageLogsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.UsageLogsTable,
Columns: []string{group.UsageLogsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UsageLogsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.UsageLogsTable,
Columns: []string{group.UsageLogsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.AccountsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
......@@ -1065,6 +1174,27 @@ func (_u *GroupUpdateOne) ClearMonthlyLimitUsd() *GroupUpdateOne {
return _u
}
// SetDefaultValidityDays sets the "default_validity_days" field.
func (_u *GroupUpdateOne) SetDefaultValidityDays(v int) *GroupUpdateOne {
_u.mutation.ResetDefaultValidityDays()
_u.mutation.SetDefaultValidityDays(v)
return _u
}
// SetNillableDefaultValidityDays sets the "default_validity_days" field if the given value is not nil.
func (_u *GroupUpdateOne) SetNillableDefaultValidityDays(v *int) *GroupUpdateOne {
if v != nil {
_u.SetDefaultValidityDays(*v)
}
return _u
}
// AddDefaultValidityDays adds value to the "default_validity_days" field.
func (_u *GroupUpdateOne) AddDefaultValidityDays(v int) *GroupUpdateOne {
_u.mutation.AddDefaultValidityDays(v)
return _u
}
// AddAPIKeyIDs adds the "api_keys" edge to the ApiKey entity by IDs.
func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne {
_u.mutation.AddAPIKeyIDs(ids...)
......@@ -1110,6 +1240,21 @@ func (_u *GroupUpdateOne) AddSubscriptions(v ...*UserSubscription) *GroupUpdateO
return _u.AddSubscriptionIDs(ids...)
}
// AddUsageLogIDs adds the "usage_logs" edge to the UsageLog entity by IDs.
func (_u *GroupUpdateOne) AddUsageLogIDs(ids ...int64) *GroupUpdateOne {
_u.mutation.AddUsageLogIDs(ids...)
return _u
}
// AddUsageLogs adds the "usage_logs" edges to the UsageLog entity.
func (_u *GroupUpdateOne) AddUsageLogs(v ...*UsageLog) *GroupUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddUsageLogIDs(ids...)
}
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
func (_u *GroupUpdateOne) AddAccountIDs(ids ...int64) *GroupUpdateOne {
_u.mutation.AddAccountIDs(ids...)
......@@ -1208,6 +1353,27 @@ func (_u *GroupUpdateOne) RemoveSubscriptions(v ...*UserSubscription) *GroupUpda
return _u.RemoveSubscriptionIDs(ids...)
}
// ClearUsageLogs clears all "usage_logs" edges to the UsageLog entity.
func (_u *GroupUpdateOne) ClearUsageLogs() *GroupUpdateOne {
_u.mutation.ClearUsageLogs()
return _u
}
// RemoveUsageLogIDs removes the "usage_logs" edge to UsageLog entities by IDs.
func (_u *GroupUpdateOne) RemoveUsageLogIDs(ids ...int64) *GroupUpdateOne {
_u.mutation.RemoveUsageLogIDs(ids...)
return _u
}
// RemoveUsageLogs removes "usage_logs" edges to UsageLog entities.
func (_u *GroupUpdateOne) RemoveUsageLogs(v ...*UsageLog) *GroupUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveUsageLogIDs(ids...)
}
// ClearAccounts clears all "accounts" edges to the Account entity.
func (_u *GroupUpdateOne) ClearAccounts() *GroupUpdateOne {
_u.mutation.ClearAccounts()
......@@ -1422,6 +1588,12 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
if _u.mutation.MonthlyLimitUsdCleared() {
_spec.ClearField(group.FieldMonthlyLimitUsd, field.TypeFloat64)
}
if value, ok := _u.mutation.DefaultValidityDays(); ok {
_spec.SetField(group.FieldDefaultValidityDays, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedDefaultValidityDays(); ok {
_spec.AddField(group.FieldDefaultValidityDays, field.TypeInt, value)
}
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
......@@ -1557,6 +1729,51 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.UsageLogsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.UsageLogsTable,
Columns: []string{group.UsageLogsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedUsageLogsIDs(); len(nodes) > 0 && !_u.mutation.UsageLogsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.UsageLogsTable,
Columns: []string{group.UsageLogsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.UsageLogsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: group.UsageLogsTable,
Columns: []string{group.UsageLogsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(usagelog.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.AccountsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2M,
......
......@@ -93,6 +93,18 @@ func (f SettingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m)
}
// The UsageLogFunc type is an adapter to allow the use of ordinary
// function as UsageLog mutator.
type UsageLogFunc func(context.Context, *ent.UsageLogMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f UsageLogFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.UsageLogMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UsageLogMutation", m)
}
// The UserFunc type is an adapter to allow the use of ordinary
// function as User mutator.
type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error)
......
......@@ -16,6 +16,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
......@@ -266,6 +267,33 @@ func (f TraverseSetting) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q)
}
// The UsageLogFunc type is an adapter to allow the use of ordinary function as a Querier.
type UsageLogFunc func(context.Context, *ent.UsageLogQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f UsageLogFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.UsageLogQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.UsageLogQuery", q)
}
// The TraverseUsageLog type is an adapter to allow the use of ordinary function as Traverser.
type TraverseUsageLog func(context.Context, *ent.UsageLogQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseUsageLog) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseUsageLog) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.UsageLogQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.UsageLogQuery", q)
}
// The UserFunc type is an adapter to allow the use of ordinary function as a Querier.
type UserFunc func(context.Context, *ent.UserQuery) (ent.Value, error)
......@@ -364,6 +392,8 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.RedeemCodeQuery, predicate.RedeemCode, redeemcode.OrderOption]{typ: ent.TypeRedeemCode, tq: q}, nil
case *ent.SettingQuery:
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
case *ent.UsageLogQuery:
return &query[*ent.UsageLogQuery, predicate.UsageLog, usagelog.OrderOption]{typ: ent.TypeUsageLog, tq: q}, nil
case *ent.UserQuery:
return &query[*ent.UserQuery, predicate.User, user.OrderOption]{typ: ent.TypeUser, tq: q}, nil
case *ent.UserAllowedGroupQuery:
......
......@@ -20,7 +20,6 @@ var (
{Name: "type", Type: field.TypeString, Size: 20},
{Name: "credentials", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "extra", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
{Name: "proxy_id", Type: field.TypeInt64, Nullable: true},
{Name: "concurrency", Type: field.TypeInt, Default: 3},
{Name: "priority", Type: field.TypeInt, Default: 50},
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
......@@ -33,12 +32,21 @@ var (
{Name: "session_window_start", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "session_window_end", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "session_window_status", Type: field.TypeString, Nullable: true, Size: 20},
{Name: "proxy_id", Type: field.TypeInt64, Nullable: true},
}
// AccountsTable holds the schema information for the "accounts" table.
AccountsTable = &schema.Table{
Name: "accounts",
Columns: AccountsColumns,
PrimaryKey: []*schema.Column{AccountsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "accounts_proxies_proxy",
Columns: []*schema.Column{AccountsColumns[21]},
RefColumns: []*schema.Column{ProxiesColumns[0]},
OnDelete: schema.SetNull,
},
},
Indexes: []*schema.Index{
{
Name: "account_platform",
......@@ -53,42 +61,42 @@ var (
{
Name: "account_status",
Unique: false,
Columns: []*schema.Column{AccountsColumns[12]},
Columns: []*schema.Column{AccountsColumns[11]},
},
{
Name: "account_proxy_id",
Unique: false,
Columns: []*schema.Column{AccountsColumns[9]},
Columns: []*schema.Column{AccountsColumns[21]},
},
{
Name: "account_priority",
Unique: false,
Columns: []*schema.Column{AccountsColumns[11]},
Columns: []*schema.Column{AccountsColumns[10]},
},
{
Name: "account_last_used_at",
Unique: false,
Columns: []*schema.Column{AccountsColumns[14]},
Columns: []*schema.Column{AccountsColumns[13]},
},
{
Name: "account_schedulable",
Unique: false,
Columns: []*schema.Column{AccountsColumns[15]},
Columns: []*schema.Column{AccountsColumns[14]},
},
{
Name: "account_rate_limited_at",
Unique: false,
Columns: []*schema.Column{AccountsColumns[16]},
Columns: []*schema.Column{AccountsColumns[15]},
},
{
Name: "account_rate_limit_reset_at",
Unique: false,
Columns: []*schema.Column{AccountsColumns[17]},
Columns: []*schema.Column{AccountsColumns[16]},
},
{
Name: "account_overload_until",
Unique: false,
Columns: []*schema.Column{AccountsColumns[18]},
Columns: []*schema.Column{AccountsColumns[17]},
},
{
Name: "account_deleted_at",
......@@ -100,7 +108,7 @@ var (
// AccountGroupsColumns holds the columns for the "account_groups" table.
AccountGroupsColumns = []*schema.Column{
{Name: "priority", Type: field.TypeInt, Default: 50},
{Name: "created_at", Type: field.TypeTime},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "account_id", Type: field.TypeInt64},
{Name: "group_id", Type: field.TypeInt64},
}
......@@ -168,11 +176,6 @@ var (
},
},
Indexes: []*schema.Index{
{
Name: "apikey_key",
Unique: true,
Columns: []*schema.Column{APIKeysColumns[4]},
},
{
Name: "apikey_user_id",
Unique: false,
......@@ -211,6 +214,7 @@ var (
{Name: "daily_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "weekly_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "monthly_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "default_validity_days", Type: field.TypeInt, Default: 30},
}
// GroupsTable holds the schema information for the "groups" table.
GroupsTable = &schema.Table{
......@@ -218,11 +222,6 @@ var (
Columns: GroupsColumns,
PrimaryKey: []*schema.Column{GroupsColumns[0]},
Indexes: []*schema.Index{
{
Name: "group_name",
Unique: true,
Columns: []*schema.Column{GroupsColumns[4]},
},
{
Name: "group_status",
Unique: false,
......@@ -316,11 +315,6 @@ var (
},
},
Indexes: []*schema.Index{
{
Name: "redeemcode_code",
Unique: true,
Columns: []*schema.Column{RedeemCodesColumns[1]},
},
{
Name: "redeemcode_status",
Unique: false,
......@@ -350,11 +344,123 @@ var (
Name: "settings",
Columns: SettingsColumns,
PrimaryKey: []*schema.Column{SettingsColumns[0]},
}
// UsageLogsColumns holds the columns for the "usage_logs" table.
UsageLogsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "request_id", Type: field.TypeString, Size: 64},
{Name: "model", Type: field.TypeString, Size: 100},
{Name: "input_tokens", Type: field.TypeInt, Default: 0},
{Name: "output_tokens", Type: field.TypeInt, Default: 0},
{Name: "cache_creation_tokens", Type: field.TypeInt, Default: 0},
{Name: "cache_read_tokens", Type: field.TypeInt, Default: 0},
{Name: "cache_creation_5m_tokens", Type: field.TypeInt, Default: 0},
{Name: "cache_creation_1h_tokens", Type: field.TypeInt, Default: 0},
{Name: "input_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "output_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "cache_creation_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "cache_read_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "total_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "actual_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}},
{Name: "rate_multiplier", Type: field.TypeFloat64, Default: 1, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "billing_type", Type: field.TypeInt8, Default: 0},
{Name: "stream", Type: field.TypeBool, Default: false},
{Name: "duration_ms", Type: field.TypeInt, Nullable: true},
{Name: "first_token_ms", Type: field.TypeInt, Nullable: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "account_id", Type: field.TypeInt64},
{Name: "api_key_id", Type: field.TypeInt64},
{Name: "group_id", Type: field.TypeInt64, Nullable: true},
{Name: "user_id", Type: field.TypeInt64},
{Name: "subscription_id", Type: field.TypeInt64, Nullable: true},
}
// UsageLogsTable holds the schema information for the "usage_logs" table.
UsageLogsTable = &schema.Table{
Name: "usage_logs",
Columns: UsageLogsColumns,
PrimaryKey: []*schema.Column{UsageLogsColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "usage_logs_accounts_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[21]},
RefColumns: []*schema.Column{AccountsColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_api_keys_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[22]},
RefColumns: []*schema.Column{APIKeysColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_groups_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[23]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull,
},
{
Symbol: "usage_logs_users_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[24]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "usage_logs_user_subscriptions_usage_logs",
Columns: []*schema.Column{UsageLogsColumns[25]},
RefColumns: []*schema.Column{UserSubscriptionsColumns[0]},
OnDelete: schema.SetNull,
},
},
Indexes: []*schema.Index{
{
Name: "setting_key",
Unique: true,
Columns: []*schema.Column{SettingsColumns[1]},
Name: "usagelog_user_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[24]},
},
{
Name: "usagelog_api_key_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[22]},
},
{
Name: "usagelog_account_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[21]},
},
{
Name: "usagelog_group_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[23]},
},
{
Name: "usagelog_subscription_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[25]},
},
{
Name: "usagelog_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[20]},
},
{
Name: "usagelog_model",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[2]},
},
{
Name: "usagelog_request_id",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[1]},
},
{
Name: "usagelog_user_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[24], UsageLogsColumns[20]},
},
{
Name: "usagelog_api_key_id_created_at",
Unique: false,
Columns: []*schema.Column{UsageLogsColumns[22], UsageLogsColumns[20]},
},
},
}
......@@ -380,11 +486,6 @@ var (
Columns: UsersColumns,
PrimaryKey: []*schema.Column{UsersColumns[0]},
Indexes: []*schema.Index{
{
Name: "user_email",
Unique: true,
Columns: []*schema.Column{UsersColumns[4]},
},
{
Name: "user_status",
Unique: false,
......@@ -399,7 +500,7 @@ var (
}
// UserAllowedGroupsColumns holds the columns for the "user_allowed_groups" table.
UserAllowedGroupsColumns = []*schema.Column{
{Name: "created_at", Type: field.TypeTime},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "user_id", Type: field.TypeInt64},
{Name: "group_id", Type: field.TypeInt64},
}
......@@ -435,6 +536,7 @@ var (
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "starts_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "expires_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
......@@ -458,19 +560,19 @@ var (
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "user_subscriptions_groups_subscriptions",
Columns: []*schema.Column{UserSubscriptionsColumns[14]},
Columns: []*schema.Column{UserSubscriptionsColumns[15]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "user_subscriptions_users_subscriptions",
Columns: []*schema.Column{UserSubscriptionsColumns[15]},
Columns: []*schema.Column{UserSubscriptionsColumns[16]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
{
Symbol: "user_subscriptions_users_assigned_subscriptions",
Columns: []*schema.Column{UserSubscriptionsColumns[16]},
Columns: []*schema.Column{UserSubscriptionsColumns[17]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.SetNull,
},
......@@ -479,32 +581,37 @@ var (
{
Name: "usersubscription_user_id",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[15]},
Columns: []*schema.Column{UserSubscriptionsColumns[16]},
},
{
Name: "usersubscription_group_id",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[14]},
Columns: []*schema.Column{UserSubscriptionsColumns[15]},
},
{
Name: "usersubscription_status",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[5]},
Columns: []*schema.Column{UserSubscriptionsColumns[6]},
},
{
Name: "usersubscription_expires_at",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[4]},
Columns: []*schema.Column{UserSubscriptionsColumns[5]},
},
{
Name: "usersubscription_assigned_by",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[16]},
Columns: []*schema.Column{UserSubscriptionsColumns[17]},
},
{
Name: "usersubscription_user_id_group_id",
Unique: true,
Columns: []*schema.Column{UserSubscriptionsColumns[15], UserSubscriptionsColumns[14]},
Columns: []*schema.Column{UserSubscriptionsColumns[16], UserSubscriptionsColumns[15]},
},
{
Name: "usersubscription_deleted_at",
Unique: false,
Columns: []*schema.Column{UserSubscriptionsColumns[3]},
},
},
}
......@@ -517,6 +624,7 @@ var (
ProxiesTable,
RedeemCodesTable,
SettingsTable,
UsageLogsTable,
UsersTable,
UserAllowedGroupsTable,
UserSubscriptionsTable,
......@@ -524,6 +632,7 @@ var (
)
func init() {
AccountsTable.ForeignKeys[0].RefTable = ProxiesTable
AccountsTable.Annotation = &entsql.Annotation{
Table: "accounts",
}
......@@ -551,6 +660,14 @@ func init() {
SettingsTable.Annotation = &entsql.Annotation{
Table: "settings",
}
UsageLogsTable.ForeignKeys[0].RefTable = AccountsTable
UsageLogsTable.ForeignKeys[1].RefTable = APIKeysTable
UsageLogsTable.ForeignKeys[2].RefTable = GroupsTable
UsageLogsTable.ForeignKeys[3].RefTable = UsersTable
UsageLogsTable.ForeignKeys[4].RefTable = UserSubscriptionsTable
UsageLogsTable.Annotation = &entsql.Annotation{
Table: "usage_logs",
}
UsersTable.Annotation = &entsql.Annotation{
Table: "users",
}
......
This diff is collapsed.
......@@ -27,6 +27,9 @@ type RedeemCode func(*sql.Selector)
// Setting is the predicate function for setting builders.
type Setting func(*sql.Selector)
// UsageLog is the predicate function for usagelog builders.
type UsageLog func(*sql.Selector)
// User is the predicate function for user builders.
type User func(*sql.Selector)
......
......@@ -37,9 +37,30 @@ type Proxy struct {
Password *string `json:"password,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the ProxyQuery when eager-loading is set.
Edges ProxyEdges `json:"edges"`
selectValues sql.SelectValues
}
// ProxyEdges holds the relations/edges for other nodes in the graph.
type ProxyEdges struct {
// Accounts holds the value of the accounts edge.
Accounts []*Account `json:"accounts,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// AccountsOrErr returns the Accounts value or an error if the edge
// was not loaded in eager-loading.
func (e ProxyEdges) AccountsOrErr() ([]*Account, error) {
if e.loadedTypes[0] {
return e.Accounts, nil
}
return nil, &NotLoadedError{edge: "accounts"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*Proxy) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
......@@ -148,6 +169,11 @@ func (_m *Proxy) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryAccounts queries the "accounts" edge of the Proxy entity.
func (_m *Proxy) QueryAccounts() *AccountQuery {
return NewProxyClient(_m.config).QueryAccounts(_m)
}
// Update returns a builder for updating this Proxy.
// Note that you need to call Proxy.Unwrap() before calling this method if this Proxy
// was returned from a transaction, and the transaction was committed or rolled back.
......
......@@ -7,6 +7,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
......@@ -34,8 +35,17 @@ const (
FieldPassword = "password"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// EdgeAccounts holds the string denoting the accounts edge name in mutations.
EdgeAccounts = "accounts"
// Table holds the table name of the proxy in the database.
Table = "proxies"
// AccountsTable is the table that holds the accounts relation/edge.
AccountsTable = "accounts"
// AccountsInverseTable is the table name for the Account entity.
// It exists in this package in order to avoid circular dependency with the "account" package.
AccountsInverseTable = "accounts"
// AccountsColumn is the table column denoting the accounts relation/edge.
AccountsColumn = "proxy_id"
)
// Columns holds all SQL columns for proxy fields.
......@@ -150,3 +160,24 @@ func ByPassword(opts ...sql.OrderTermOption) OrderOption {
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByAccountsCount orders the results by accounts count.
func ByAccountsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newAccountsStep(), opts...)
}
}
// ByAccounts orders the results by accounts terms.
func ByAccounts(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newAccountsStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
func newAccountsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(AccountsInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, true, AccountsTable, AccountsColumn),
)
}
......@@ -6,6 +6,7 @@ import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
......@@ -684,6 +685,29 @@ func StatusContainsFold(v string) predicate.Proxy {
return predicate.Proxy(sql.FieldContainsFold(FieldStatus, v))
}
// HasAccounts applies the HasEdge predicate on the "accounts" edge.
func HasAccounts() predicate.Proxy {
return predicate.Proxy(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, true, AccountsTable, AccountsColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasAccountsWith applies the HasEdge predicate on the "accounts" edge with a given conditions (other predicates).
func HasAccountsWith(preds ...predicate.Account) predicate.Proxy {
return predicate.Proxy(func(s *sql.Selector) {
step := newAccountsStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.Proxy) predicate.Proxy {
return predicate.Proxy(sql.AndPredicates(predicates...))
......
......@@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/account"
"github.com/Wei-Shaw/sub2api/ent/proxy"
)
......@@ -130,6 +131,21 @@ func (_c *ProxyCreate) SetNillableStatus(v *string) *ProxyCreate {
return _c
}
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
func (_c *ProxyCreate) AddAccountIDs(ids ...int64) *ProxyCreate {
_c.mutation.AddAccountIDs(ids...)
return _c
}
// AddAccounts adds the "accounts" edges to the Account entity.
func (_c *ProxyCreate) AddAccounts(v ...*Account) *ProxyCreate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddAccountIDs(ids...)
}
// Mutation returns the ProxyMutation object of the builder.
func (_c *ProxyCreate) Mutation() *ProxyMutation {
return _c.mutation
......@@ -308,6 +324,22 @@ func (_c *ProxyCreate) createSpec() (*Proxy, *sqlgraph.CreateSpec) {
_spec.SetField(proxy.FieldStatus, field.TypeString, value)
_node.Status = value
}
if nodes := _c.mutation.AccountsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}
......
......@@ -4,6 +4,7 @@ package ent
import (
"context"
"database/sql/driver"
"fmt"
"math"
......@@ -11,6 +12,7 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/account"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/proxy"
)
......@@ -22,6 +24,7 @@ type ProxyQuery struct {
order []proxy.OrderOption
inters []Interceptor
predicates []predicate.Proxy
withAccounts *AccountQuery
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
......@@ -58,6 +61,28 @@ func (_q *ProxyQuery) Order(o ...proxy.OrderOption) *ProxyQuery {
return _q
}
// QueryAccounts chains the current query on the "accounts" edge.
func (_q *ProxyQuery) QueryAccounts() *AccountQuery {
query := (&AccountClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(proxy.Table, proxy.FieldID, selector),
sqlgraph.To(account.Table, account.FieldID),
sqlgraph.Edge(sqlgraph.O2M, true, proxy.AccountsTable, proxy.AccountsColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first Proxy entity from the query.
// Returns a *NotFoundError when no Proxy was found.
func (_q *ProxyQuery) First(ctx context.Context) (*Proxy, error) {
......@@ -250,12 +275,24 @@ func (_q *ProxyQuery) Clone() *ProxyQuery {
order: append([]proxy.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.Proxy{}, _q.predicates...),
withAccounts: _q.withAccounts.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithAccounts tells the query-builder to eager-load the nodes that are connected to
// the "accounts" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *ProxyQuery) WithAccounts(opts ...func(*AccountQuery)) *ProxyQuery {
query := (&AccountClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withAccounts = query
return _q
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
......@@ -334,6 +371,9 @@ func (_q *ProxyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Proxy,
var (
nodes = []*Proxy{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withAccounts != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*Proxy).scanValues(nil, columns)
......@@ -341,6 +381,7 @@ func (_q *ProxyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Proxy,
_spec.Assign = func(columns []string, values []any) error {
node := &Proxy{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
for i := range hooks {
......@@ -352,9 +393,50 @@ func (_q *ProxyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Proxy,
if len(nodes) == 0 {
return nodes, nil
}
if query := _q.withAccounts; query != nil {
if err := _q.loadAccounts(ctx, query, nodes,
func(n *Proxy) { n.Edges.Accounts = []*Account{} },
func(n *Proxy, e *Account) { n.Edges.Accounts = append(n.Edges.Accounts, e) }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *ProxyQuery) loadAccounts(ctx context.Context, query *AccountQuery, nodes []*Proxy, init func(*Proxy), assign func(*Proxy, *Account)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*Proxy)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(account.FieldProxyID)
}
query.Where(predicate.Account(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(proxy.AccountsColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.ProxyID
if fk == nil {
return fmt.Errorf(`foreign-key "proxy_id" is nil for node %v`, n.ID)
}
node, ok := nodeids[*fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "proxy_id" returned %v for node %v`, *fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *ProxyQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
_spec.Node.Columns = _q.ctx.Fields
......
......@@ -11,6 +11,7 @@ import (
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/account"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/proxy"
)
......@@ -171,11 +172,47 @@ func (_u *ProxyUpdate) SetNillableStatus(v *string) *ProxyUpdate {
return _u
}
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
func (_u *ProxyUpdate) AddAccountIDs(ids ...int64) *ProxyUpdate {
_u.mutation.AddAccountIDs(ids...)
return _u
}
// AddAccounts adds the "accounts" edges to the Account entity.
func (_u *ProxyUpdate) AddAccounts(v ...*Account) *ProxyUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddAccountIDs(ids...)
}
// Mutation returns the ProxyMutation object of the builder.
func (_u *ProxyUpdate) Mutation() *ProxyMutation {
return _u.mutation
}
// ClearAccounts clears all "accounts" edges to the Account entity.
func (_u *ProxyUpdate) ClearAccounts() *ProxyUpdate {
_u.mutation.ClearAccounts()
return _u
}
// RemoveAccountIDs removes the "accounts" edge to Account entities by IDs.
func (_u *ProxyUpdate) RemoveAccountIDs(ids ...int64) *ProxyUpdate {
_u.mutation.RemoveAccountIDs(ids...)
return _u
}
// RemoveAccounts removes "accounts" edges to Account entities.
func (_u *ProxyUpdate) RemoveAccounts(v ...*Account) *ProxyUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveAccountIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *ProxyUpdate) Save(ctx context.Context) (int, error) {
if err := _u.defaults(); err != nil {
......@@ -304,6 +341,51 @@ func (_u *ProxyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(proxy.FieldStatus, field.TypeString, value)
}
if _u.mutation.AccountsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedAccountsIDs(); len(nodes) > 0 && !_u.mutation.AccountsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.AccountsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{proxy.Label}
......@@ -467,11 +549,47 @@ func (_u *ProxyUpdateOne) SetNillableStatus(v *string) *ProxyUpdateOne {
return _u
}
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
func (_u *ProxyUpdateOne) AddAccountIDs(ids ...int64) *ProxyUpdateOne {
_u.mutation.AddAccountIDs(ids...)
return _u
}
// AddAccounts adds the "accounts" edges to the Account entity.
func (_u *ProxyUpdateOne) AddAccounts(v ...*Account) *ProxyUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddAccountIDs(ids...)
}
// Mutation returns the ProxyMutation object of the builder.
func (_u *ProxyUpdateOne) Mutation() *ProxyMutation {
return _u.mutation
}
// ClearAccounts clears all "accounts" edges to the Account entity.
func (_u *ProxyUpdateOne) ClearAccounts() *ProxyUpdateOne {
_u.mutation.ClearAccounts()
return _u
}
// RemoveAccountIDs removes the "accounts" edge to Account entities by IDs.
func (_u *ProxyUpdateOne) RemoveAccountIDs(ids ...int64) *ProxyUpdateOne {
_u.mutation.RemoveAccountIDs(ids...)
return _u
}
// RemoveAccounts removes "accounts" edges to Account entities.
func (_u *ProxyUpdateOne) RemoveAccounts(v ...*Account) *ProxyUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemoveAccountIDs(ids...)
}
// Where appends a list predicates to the ProxyUpdate builder.
func (_u *ProxyUpdateOne) Where(ps ...predicate.Proxy) *ProxyUpdateOne {
_u.mutation.Where(ps...)
......@@ -630,6 +748,51 @@ func (_u *ProxyUpdateOne) sqlSave(ctx context.Context) (_node *Proxy, err error)
if value, ok := _u.mutation.Status(); ok {
_spec.SetField(proxy.FieldStatus, field.TypeString, value)
}
if _u.mutation.AccountsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedAccountsIDs(); len(nodes) > 0 && !_u.mutation.AccountsCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.AccountsIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: true,
Table: proxy.AccountsTable,
Columns: []string{proxy.AccountsColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &Proxy{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
......
......@@ -13,6 +13,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/schema"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
......@@ -259,6 +260,10 @@ func init() {
group.DefaultSubscriptionType = groupDescSubscriptionType.Default.(string)
// group.SubscriptionTypeValidator is a validator for the "subscription_type" field. It is called by the builders before save.
group.SubscriptionTypeValidator = groupDescSubscriptionType.Validators[0].(func(string) error)
// groupDescDefaultValidityDays is the schema descriptor for default_validity_days field.
groupDescDefaultValidityDays := groupFields[10].Descriptor()
// group.DefaultDefaultValidityDays holds the default value on creation for the default_validity_days field.
group.DefaultDefaultValidityDays = groupDescDefaultValidityDays.Default.(int)
proxyMixin := schema.Proxy{}.Mixin()
proxyMixinHooks1 := proxyMixin[1].Hooks()
proxy.Hooks[0] = proxyMixinHooks1[0]
......@@ -420,6 +425,108 @@ func init() {
setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time)
// setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time)
usagelogFields := schema.UsageLog{}.Fields()
_ = usagelogFields
// usagelogDescRequestID is the schema descriptor for request_id field.
usagelogDescRequestID := usagelogFields[3].Descriptor()
// usagelog.RequestIDValidator is a validator for the "request_id" field. It is called by the builders before save.
usagelog.RequestIDValidator = func() func(string) error {
validators := usagelogDescRequestID.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(request_id string) error {
for _, fn := range fns {
if err := fn(request_id); err != nil {
return err
}
}
return nil
}
}()
// usagelogDescModel is the schema descriptor for model field.
usagelogDescModel := usagelogFields[4].Descriptor()
// usagelog.ModelValidator is a validator for the "model" field. It is called by the builders before save.
usagelog.ModelValidator = func() func(string) error {
validators := usagelogDescModel.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(model string) error {
for _, fn := range fns {
if err := fn(model); err != nil {
return err
}
}
return nil
}
}()
// usagelogDescInputTokens is the schema descriptor for input_tokens field.
usagelogDescInputTokens := usagelogFields[7].Descriptor()
// usagelog.DefaultInputTokens holds the default value on creation for the input_tokens field.
usagelog.DefaultInputTokens = usagelogDescInputTokens.Default.(int)
// usagelogDescOutputTokens is the schema descriptor for output_tokens field.
usagelogDescOutputTokens := usagelogFields[8].Descriptor()
// usagelog.DefaultOutputTokens holds the default value on creation for the output_tokens field.
usagelog.DefaultOutputTokens = usagelogDescOutputTokens.Default.(int)
// usagelogDescCacheCreationTokens is the schema descriptor for cache_creation_tokens field.
usagelogDescCacheCreationTokens := usagelogFields[9].Descriptor()
// usagelog.DefaultCacheCreationTokens holds the default value on creation for the cache_creation_tokens field.
usagelog.DefaultCacheCreationTokens = usagelogDescCacheCreationTokens.Default.(int)
// usagelogDescCacheReadTokens is the schema descriptor for cache_read_tokens field.
usagelogDescCacheReadTokens := usagelogFields[10].Descriptor()
// usagelog.DefaultCacheReadTokens holds the default value on creation for the cache_read_tokens field.
usagelog.DefaultCacheReadTokens = usagelogDescCacheReadTokens.Default.(int)
// usagelogDescCacheCreation5mTokens is the schema descriptor for cache_creation_5m_tokens field.
usagelogDescCacheCreation5mTokens := usagelogFields[11].Descriptor()
// usagelog.DefaultCacheCreation5mTokens holds the default value on creation for the cache_creation_5m_tokens field.
usagelog.DefaultCacheCreation5mTokens = usagelogDescCacheCreation5mTokens.Default.(int)
// usagelogDescCacheCreation1hTokens is the schema descriptor for cache_creation_1h_tokens field.
usagelogDescCacheCreation1hTokens := usagelogFields[12].Descriptor()
// usagelog.DefaultCacheCreation1hTokens holds the default value on creation for the cache_creation_1h_tokens field.
usagelog.DefaultCacheCreation1hTokens = usagelogDescCacheCreation1hTokens.Default.(int)
// usagelogDescInputCost is the schema descriptor for input_cost field.
usagelogDescInputCost := usagelogFields[13].Descriptor()
// usagelog.DefaultInputCost holds the default value on creation for the input_cost field.
usagelog.DefaultInputCost = usagelogDescInputCost.Default.(float64)
// usagelogDescOutputCost is the schema descriptor for output_cost field.
usagelogDescOutputCost := usagelogFields[14].Descriptor()
// usagelog.DefaultOutputCost holds the default value on creation for the output_cost field.
usagelog.DefaultOutputCost = usagelogDescOutputCost.Default.(float64)
// usagelogDescCacheCreationCost is the schema descriptor for cache_creation_cost field.
usagelogDescCacheCreationCost := usagelogFields[15].Descriptor()
// usagelog.DefaultCacheCreationCost holds the default value on creation for the cache_creation_cost field.
usagelog.DefaultCacheCreationCost = usagelogDescCacheCreationCost.Default.(float64)
// usagelogDescCacheReadCost is the schema descriptor for cache_read_cost field.
usagelogDescCacheReadCost := usagelogFields[16].Descriptor()
// usagelog.DefaultCacheReadCost holds the default value on creation for the cache_read_cost field.
usagelog.DefaultCacheReadCost = usagelogDescCacheReadCost.Default.(float64)
// usagelogDescTotalCost is the schema descriptor for total_cost field.
usagelogDescTotalCost := usagelogFields[17].Descriptor()
// usagelog.DefaultTotalCost holds the default value on creation for the total_cost field.
usagelog.DefaultTotalCost = usagelogDescTotalCost.Default.(float64)
// usagelogDescActualCost is the schema descriptor for actual_cost field.
usagelogDescActualCost := usagelogFields[18].Descriptor()
// usagelog.DefaultActualCost holds the default value on creation for the actual_cost field.
usagelog.DefaultActualCost = usagelogDescActualCost.Default.(float64)
// usagelogDescRateMultiplier is the schema descriptor for rate_multiplier field.
usagelogDescRateMultiplier := usagelogFields[19].Descriptor()
// usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
usagelog.DefaultRateMultiplier = usagelogDescRateMultiplier.Default.(float64)
// usagelogDescBillingType is the schema descriptor for billing_type field.
usagelogDescBillingType := usagelogFields[20].Descriptor()
// usagelog.DefaultBillingType holds the default value on creation for the billing_type field.
usagelog.DefaultBillingType = usagelogDescBillingType.Default.(int8)
// usagelogDescStream is the schema descriptor for stream field.
usagelogDescStream := usagelogFields[21].Descriptor()
// usagelog.DefaultStream holds the default value on creation for the stream field.
usagelog.DefaultStream = usagelogDescStream.Default.(bool)
// usagelogDescCreatedAt is the schema descriptor for created_at field.
usagelogDescCreatedAt := usagelogFields[24].Descriptor()
// usagelog.DefaultCreatedAt holds the default value on creation for the created_at field.
usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time)
userMixin := schema.User{}.Mixin()
userMixinHooks1 := userMixin[1].Hooks()
user.Hooks[0] = userMixinHooks1[0]
......@@ -518,6 +625,10 @@ func init() {
// userallowedgroup.DefaultCreatedAt holds the default value on creation for the created_at field.
userallowedgroup.DefaultCreatedAt = userallowedgroupDescCreatedAt.Default.(func() time.Time)
usersubscriptionMixin := schema.UserSubscription{}.Mixin()
usersubscriptionMixinHooks1 := usersubscriptionMixin[1].Hooks()
usersubscription.Hooks[0] = usersubscriptionMixinHooks1[0]
usersubscriptionMixinInters1 := usersubscriptionMixin[1].Interceptors()
usersubscription.Interceptors[0] = usersubscriptionMixinInters1[0]
usersubscriptionMixinFields0 := usersubscriptionMixin[0].Fields()
_ = usersubscriptionMixinFields0
usersubscriptionFields := schema.UserSubscription{}.Fields()
......
......@@ -168,6 +168,13 @@ func (Account) Edges() []ent.Edge {
// 一个账户可以属于多个分组,一个分组可以包含多个账户
edge.To("groups", Group.Type).
Through("account_groups", AccountGroup.Type),
// proxy: 账户使用的代理配置(可选的一对一关系)
// 使用已有的 proxy_id 外键字段
edge.To("proxy", Proxy.Type).
Field("proxy_id").
Unique(),
// usage_logs: 该账户的使用日志
edge.To("usage_logs", UsageLog.Type),
}
}
......
......@@ -4,6 +4,7 @@ import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
......@@ -33,7 +34,8 @@ func (AccountGroup) Fields() []ent.Field {
Default(50),
field.Time("created_at").
Immutable().
Default(time.Now),
Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
......
......@@ -60,12 +60,13 @@ func (ApiKey) Edges() []ent.Edge {
Ref("api_keys").
Field("group_id").
Unique(),
edge.To("usage_logs", UsageLog.Type),
}
}
func (ApiKey) Indexes() []ent.Index {
return []ent.Index{
index.Fields("key").Unique(),
// key 字段已在 Fields() 中声明 Unique(),无需重复索引
index.Fields("user_id"),
index.Fields("group_id"),
index.Fields("status"),
......
......@@ -69,6 +69,8 @@ func (Group) Fields() []ent.Field {
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Int("default_validity_days").
Default(30),
}
}
......@@ -77,6 +79,7 @@ func (Group) Edges() []ent.Edge {
edge.To("api_keys", ApiKey.Type),
edge.To("redeem_codes", RedeemCode.Type),
edge.To("subscriptions", UserSubscription.Type),
edge.To("usage_logs", UsageLog.Type),
edge.From("accounts", Account.Type).
Ref("groups").
Through("account_groups", AccountGroup.Type),
......@@ -88,7 +91,7 @@ func (Group) Edges() []ent.Edge {
func (Group) Indexes() []ent.Index {
return []ent.Index{
index.Fields("name").Unique(),
// name 字段已在 Fields() 中声明 Unique(),无需重复索引
index.Fields("status"),
index.Fields("platform"),
index.Fields("subscription_type"),
......
......@@ -6,6 +6,7 @@ import (
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
......@@ -54,6 +55,15 @@ func (Proxy) Fields() []ent.Field {
}
}
// Edges 定义代理实体的关联关系。
func (Proxy) Edges() []ent.Edge {
return []ent.Edge{
// accounts: 使用此代理的账户(反向边)
edge.From("accounts", Account.Type).
Ref("proxy"),
}
}
func (Proxy) Indexes() []ent.Index {
return []ent.Index{
index.Fields("status"),
......
......@@ -15,6 +15,14 @@ import (
)
// RedeemCode holds the schema definition for the RedeemCode entity.
//
// 删除策略:硬删除
// RedeemCode 使用硬删除而非软删除,原因如下:
// - 兑换码具有一次性使用特性,删除后无需保留历史记录
// - 已使用的兑换码通过 status 和 used_at 字段追踪,无需依赖软删除
// - 减少数据库存储压力和查询复杂度
//
// 如需审计已删除的兑换码,建议在删除前将关键信息写入审计日志表。
type RedeemCode struct {
ent.Schema
}
......@@ -78,7 +86,7 @@ func (RedeemCode) Edges() []ent.Edge {
func (RedeemCode) Indexes() []ent.Index {
return []ent.Index{
index.Fields("code").Unique(),
// code 字段已在 Fields() 中声明 Unique(),无需重复索引
index.Fields("status"),
index.Fields("used_by"),
index.Fields("group_id"),
......
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