diff --git a/.gitignore b/.gitignore
index 93ae19f3..a50f3ecc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,9 @@ backend/server
backend/sub2api
backend/main
+# Go 测试二进制
+*.test
+
# 测试覆盖率
*.out
coverage.html
diff --git a/backend/cmd/jwtgen/main.go b/backend/cmd/jwtgen/main.go
index 1b7f4aa4..c461198b 100644
--- a/backend/cmd/jwtgen/main.go
+++ b/backend/cmd/jwtgen/main.go
@@ -33,7 +33,7 @@ func main() {
}()
userRepo := repository.NewUserRepository(client, sqlDB)
- authService := service.NewAuthService(userRepo, cfg, nil, nil, nil, nil)
+ authService := service.NewAuthService(userRepo, cfg, nil, nil, nil, nil, nil)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go
index 1041cce1..58f8cebf 100644
--- a/backend/cmd/server/wire_gen.go
+++ b/backend/cmd/server/wire_gen.go
@@ -51,13 +51,17 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
turnstileVerifier := repository.NewTurnstileVerifier()
turnstileService := service.NewTurnstileService(settingService, turnstileVerifier)
emailQueueService := service.ProvideEmailQueueService(emailService)
- authService := service.NewAuthService(userRepository, configConfig, settingService, emailService, turnstileService, emailQueueService)
+ promoCodeRepository := repository.NewPromoCodeRepository(client)
+ billingCache := repository.NewBillingCache(redisClient)
+ userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
+ billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig)
+ promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client)
+ authService := service.NewAuthService(userRepository, configConfig, settingService, emailService, turnstileService, emailQueueService, promoService)
userService := service.NewUserService(userRepository)
- authHandler := handler.NewAuthHandler(configConfig, authService, userService, settingService)
+ authHandler := handler.NewAuthHandler(configConfig, authService, userService, settingService, promoService)
userHandler := handler.NewUserHandler(userService)
apiKeyRepository := repository.NewAPIKeyRepository(client)
groupRepository := repository.NewGroupRepository(client, db)
- userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
apiKeyCache := repository.NewAPIKeyCache(redisClient)
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, apiKeyCache, configConfig)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
@@ -65,8 +69,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
usageService := service.NewUsageService(usageLogRepository, userRepository, client)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
redeemCodeRepository := repository.NewRedeemCodeRepository(client)
- billingCache := repository.NewBillingCache(redisClient)
- billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService)
redeemCache := repository.NewRedeemCache(redisClient)
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client)
@@ -112,6 +114,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
antigravityOAuthHandler := admin.NewAntigravityOAuthHandler(antigravityOAuthService)
proxyHandler := admin.NewProxyHandler(adminService)
adminRedeemHandler := admin.NewRedeemHandler(adminService)
+ promoHandler := admin.NewPromoHandler(promoService)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService)
updateCache := repository.NewUpdateCache(redisClient)
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
@@ -124,7 +127,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
userAttributeValueRepository := repository.NewUserAttributeValueRepository(client)
userAttributeService := service.NewUserAttributeService(userAttributeDefinitionRepository, userAttributeValueRepository)
userAttributeHandler := admin.NewUserAttributeHandler(userAttributeService)
- adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, settingHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler)
+ adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler)
pricingRemoteClient := repository.ProvidePricingRemoteClient(configConfig)
pricingService, err := service.ProvidePricingService(configConfig, pricingRemoteClient)
if err != nil {
@@ -145,7 +148,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
- engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, settingService)
+ engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, settingService, redisClient)
httpServer := server.ProvideHTTPServer(configConfig, engine)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, configConfig)
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
diff --git a/backend/ent/account_query.go b/backend/ent/account_query.go
index 3e363ecd..1761fa63 100644
--- a/backend/ent/account_query.go
+++ b/backend/ent/account_query.go
@@ -9,6 +9,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -31,6 +32,7 @@ type AccountQuery struct {
withProxy *ProxyQuery
withUsageLogs *UsageLogQuery
withAccountGroups *AccountGroupQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -495,6 +497,9 @@ func (_q *AccountQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Acco
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -690,6 +695,9 @@ func (_q *AccountQuery) loadAccountGroups(ctx context.Context, query *AccountGro
func (_q *AccountQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -755,6 +763,9 @@ func (_q *AccountQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -772,6 +783,32 @@ func (_q *AccountQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *AccountQuery) ForUpdate(opts ...sql.LockOption) *AccountQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *AccountQuery) ForShare(opts ...sql.LockOption) *AccountQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// AccountGroupBy is the group-by builder for Account entities.
type AccountGroupBy struct {
selector
diff --git a/backend/ent/accountgroup_query.go b/backend/ent/accountgroup_query.go
index 98e1c3f6..d0a4f58d 100644
--- a/backend/ent/accountgroup_query.go
+++ b/backend/ent/accountgroup_query.go
@@ -8,6 +8,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/account"
@@ -25,6 +26,7 @@ type AccountGroupQuery struct {
predicates []predicate.AccountGroup
withAccount *AccountQuery
withGroup *GroupQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -347,6 +349,9 @@ func (_q *AccountGroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -432,6 +437,9 @@ func (_q *AccountGroupQuery) loadGroup(ctx context.Context, query *GroupQuery, n
func (_q *AccountGroupQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Unique = false
_spec.Node.Columns = nil
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
@@ -495,6 +503,9 @@ func (_q *AccountGroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -512,6 +523,32 @@ func (_q *AccountGroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *AccountGroupQuery) ForUpdate(opts ...sql.LockOption) *AccountGroupQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *AccountGroupQuery) ForShare(opts ...sql.LockOption) *AccountGroupQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// AccountGroupGroupBy is the group-by builder for AccountGroup entities.
type AccountGroupGroupBy struct {
selector
diff --git a/backend/ent/apikey_query.go b/backend/ent/apikey_query.go
index 6e5c0f5e..9eee4077 100644
--- a/backend/ent/apikey_query.go
+++ b/backend/ent/apikey_query.go
@@ -9,6 +9,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -29,6 +30,7 @@ type APIKeyQuery struct {
withUser *UserQuery
withGroup *GroupQuery
withUsageLogs *UsageLogQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -458,6 +460,9 @@ func (_q *APIKeyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*APIKe
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -583,6 +588,9 @@ func (_q *APIKeyQuery) loadUsageLogs(ctx context.Context, query *UsageLogQuery,
func (_q *APIKeyQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -651,6 +659,9 @@ func (_q *APIKeyQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -668,6 +679,32 @@ func (_q *APIKeyQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *APIKeyQuery) ForUpdate(opts ...sql.LockOption) *APIKeyQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *APIKeyQuery) ForShare(opts ...sql.LockOption) *APIKeyQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// APIKeyGroupBy is the group-by builder for APIKey entities.
type APIKeyGroupBy struct {
selector
diff --git a/backend/ent/client.go b/backend/ent/client.go
index 4084dac2..35cf644f 100644
--- a/backend/ent/client.go
+++ b/backend/ent/client.go
@@ -19,6 +19,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/setting"
@@ -45,6 +47,10 @@ type Client struct {
AccountGroup *AccountGroupClient
// Group is the client for interacting with the Group builders.
Group *GroupClient
+ // PromoCode is the client for interacting with the PromoCode builders.
+ PromoCode *PromoCodeClient
+ // PromoCodeUsage is the client for interacting with the PromoCodeUsage builders.
+ PromoCodeUsage *PromoCodeUsageClient
// Proxy is the client for interacting with the Proxy builders.
Proxy *ProxyClient
// RedeemCode is the client for interacting with the RedeemCode builders.
@@ -78,6 +84,8 @@ func (c *Client) init() {
c.Account = NewAccountClient(c.config)
c.AccountGroup = NewAccountGroupClient(c.config)
c.Group = NewGroupClient(c.config)
+ c.PromoCode = NewPromoCodeClient(c.config)
+ c.PromoCodeUsage = NewPromoCodeUsageClient(c.config)
c.Proxy = NewProxyClient(c.config)
c.RedeemCode = NewRedeemCodeClient(c.config)
c.Setting = NewSettingClient(c.config)
@@ -183,6 +191,8 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
Account: NewAccountClient(cfg),
AccountGroup: NewAccountGroupClient(cfg),
Group: NewGroupClient(cfg),
+ PromoCode: NewPromoCodeClient(cfg),
+ PromoCodeUsage: NewPromoCodeUsageClient(cfg),
Proxy: NewProxyClient(cfg),
RedeemCode: NewRedeemCodeClient(cfg),
Setting: NewSettingClient(cfg),
@@ -215,6 +225,8 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
Account: NewAccountClient(cfg),
AccountGroup: NewAccountGroupClient(cfg),
Group: NewGroupClient(cfg),
+ PromoCode: NewPromoCodeClient(cfg),
+ PromoCodeUsage: NewPromoCodeUsageClient(cfg),
Proxy: NewProxyClient(cfg),
RedeemCode: NewRedeemCodeClient(cfg),
Setting: NewSettingClient(cfg),
@@ -253,9 +265,9 @@ func (c *Client) Close() error {
// In order to add hooks to a specific client, call: `client.Node.Use(...)`.
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
- c.APIKey, c.Account, c.AccountGroup, c.Group, c.Proxy, c.RedeemCode, c.Setting,
- c.UsageLog, c.User, c.UserAllowedGroup, c.UserAttributeDefinition,
- c.UserAttributeValue, c.UserSubscription,
+ c.APIKey, c.Account, c.AccountGroup, c.Group, c.PromoCode, c.PromoCodeUsage,
+ c.Proxy, c.RedeemCode, c.Setting, c.UsageLog, c.User, c.UserAllowedGroup,
+ c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
} {
n.Use(hooks...)
}
@@ -265,9 +277,9 @@ func (c *Client) Use(hooks ...Hook) {
// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
- c.APIKey, c.Account, c.AccountGroup, c.Group, c.Proxy, c.RedeemCode, c.Setting,
- c.UsageLog, c.User, c.UserAllowedGroup, c.UserAttributeDefinition,
- c.UserAttributeValue, c.UserSubscription,
+ c.APIKey, c.Account, c.AccountGroup, c.Group, c.PromoCode, c.PromoCodeUsage,
+ c.Proxy, c.RedeemCode, c.Setting, c.UsageLog, c.User, c.UserAllowedGroup,
+ c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription,
} {
n.Intercept(interceptors...)
}
@@ -284,6 +296,10 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.AccountGroup.mutate(ctx, m)
case *GroupMutation:
return c.Group.mutate(ctx, m)
+ case *PromoCodeMutation:
+ return c.PromoCode.mutate(ctx, m)
+ case *PromoCodeUsageMutation:
+ return c.PromoCodeUsage.mutate(ctx, m)
case *ProxyMutation:
return c.Proxy.mutate(ctx, m)
case *RedeemCodeMutation:
@@ -1068,6 +1084,320 @@ func (c *GroupClient) mutate(ctx context.Context, m *GroupMutation) (Value, erro
}
}
+// PromoCodeClient is a client for the PromoCode schema.
+type PromoCodeClient struct {
+ config
+}
+
+// NewPromoCodeClient returns a client for the PromoCode from the given config.
+func NewPromoCodeClient(c config) *PromoCodeClient {
+ return &PromoCodeClient{config: c}
+}
+
+// Use adds a list of mutation hooks to the hooks stack.
+// A call to `Use(f, g, h)` equals to `promocode.Hooks(f(g(h())))`.
+func (c *PromoCodeClient) Use(hooks ...Hook) {
+ c.hooks.PromoCode = append(c.hooks.PromoCode, hooks...)
+}
+
+// Intercept adds a list of query interceptors to the interceptors stack.
+// A call to `Intercept(f, g, h)` equals to `promocode.Intercept(f(g(h())))`.
+func (c *PromoCodeClient) Intercept(interceptors ...Interceptor) {
+ c.inters.PromoCode = append(c.inters.PromoCode, interceptors...)
+}
+
+// Create returns a builder for creating a PromoCode entity.
+func (c *PromoCodeClient) Create() *PromoCodeCreate {
+ mutation := newPromoCodeMutation(c.config, OpCreate)
+ return &PromoCodeCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// CreateBulk returns a builder for creating a bulk of PromoCode entities.
+func (c *PromoCodeClient) CreateBulk(builders ...*PromoCodeCreate) *PromoCodeCreateBulk {
+ return &PromoCodeCreateBulk{config: c.config, builders: builders}
+}
+
+// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
+// a builder and applies setFunc on it.
+func (c *PromoCodeClient) MapCreateBulk(slice any, setFunc func(*PromoCodeCreate, int)) *PromoCodeCreateBulk {
+ rv := reflect.ValueOf(slice)
+ if rv.Kind() != reflect.Slice {
+ return &PromoCodeCreateBulk{err: fmt.Errorf("calling to PromoCodeClient.MapCreateBulk with wrong type %T, need slice", slice)}
+ }
+ builders := make([]*PromoCodeCreate, rv.Len())
+ for i := 0; i < rv.Len(); i++ {
+ builders[i] = c.Create()
+ setFunc(builders[i], i)
+ }
+ return &PromoCodeCreateBulk{config: c.config, builders: builders}
+}
+
+// Update returns an update builder for PromoCode.
+func (c *PromoCodeClient) Update() *PromoCodeUpdate {
+ mutation := newPromoCodeMutation(c.config, OpUpdate)
+ return &PromoCodeUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOne returns an update builder for the given entity.
+func (c *PromoCodeClient) UpdateOne(_m *PromoCode) *PromoCodeUpdateOne {
+ mutation := newPromoCodeMutation(c.config, OpUpdateOne, withPromoCode(_m))
+ return &PromoCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOneID returns an update builder for the given id.
+func (c *PromoCodeClient) UpdateOneID(id int64) *PromoCodeUpdateOne {
+ mutation := newPromoCodeMutation(c.config, OpUpdateOne, withPromoCodeID(id))
+ return &PromoCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// Delete returns a delete builder for PromoCode.
+func (c *PromoCodeClient) Delete() *PromoCodeDelete {
+ mutation := newPromoCodeMutation(c.config, OpDelete)
+ return &PromoCodeDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// DeleteOne returns a builder for deleting the given entity.
+func (c *PromoCodeClient) DeleteOne(_m *PromoCode) *PromoCodeDeleteOne {
+ return c.DeleteOneID(_m.ID)
+}
+
+// DeleteOneID returns a builder for deleting the given entity by its id.
+func (c *PromoCodeClient) DeleteOneID(id int64) *PromoCodeDeleteOne {
+ builder := c.Delete().Where(promocode.ID(id))
+ builder.mutation.id = &id
+ builder.mutation.op = OpDeleteOne
+ return &PromoCodeDeleteOne{builder}
+}
+
+// Query returns a query builder for PromoCode.
+func (c *PromoCodeClient) Query() *PromoCodeQuery {
+ return &PromoCodeQuery{
+ config: c.config,
+ ctx: &QueryContext{Type: TypePromoCode},
+ inters: c.Interceptors(),
+ }
+}
+
+// Get returns a PromoCode entity by its id.
+func (c *PromoCodeClient) Get(ctx context.Context, id int64) (*PromoCode, error) {
+ return c.Query().Where(promocode.ID(id)).Only(ctx)
+}
+
+// GetX is like Get, but panics if an error occurs.
+func (c *PromoCodeClient) GetX(ctx context.Context, id int64) *PromoCode {
+ obj, err := c.Get(ctx, id)
+ if err != nil {
+ panic(err)
+ }
+ return obj
+}
+
+// QueryUsageRecords queries the usage_records edge of a PromoCode.
+func (c *PromoCodeClient) QueryUsageRecords(_m *PromoCode) *PromoCodeUsageQuery {
+ query := (&PromoCodeUsageClient{config: c.config}).Query()
+ query.path = func(context.Context) (fromV *sql.Selector, _ error) {
+ id := _m.ID
+ step := sqlgraph.NewStep(
+ sqlgraph.From(promocode.Table, promocode.FieldID, id),
+ sqlgraph.To(promocodeusage.Table, promocodeusage.FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, promocode.UsageRecordsTable, promocode.UsageRecordsColumn),
+ )
+ fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
+// Hooks returns the client hooks.
+func (c *PromoCodeClient) Hooks() []Hook {
+ return c.hooks.PromoCode
+}
+
+// Interceptors returns the client interceptors.
+func (c *PromoCodeClient) Interceptors() []Interceptor {
+ return c.inters.PromoCode
+}
+
+func (c *PromoCodeClient) mutate(ctx context.Context, m *PromoCodeMutation) (Value, error) {
+ switch m.Op() {
+ case OpCreate:
+ return (&PromoCodeCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpUpdate:
+ return (&PromoCodeUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpUpdateOne:
+ return (&PromoCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpDelete, OpDeleteOne:
+ return (&PromoCodeDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
+ default:
+ return nil, fmt.Errorf("ent: unknown PromoCode mutation op: %q", m.Op())
+ }
+}
+
+// PromoCodeUsageClient is a client for the PromoCodeUsage schema.
+type PromoCodeUsageClient struct {
+ config
+}
+
+// NewPromoCodeUsageClient returns a client for the PromoCodeUsage from the given config.
+func NewPromoCodeUsageClient(c config) *PromoCodeUsageClient {
+ return &PromoCodeUsageClient{config: c}
+}
+
+// Use adds a list of mutation hooks to the hooks stack.
+// A call to `Use(f, g, h)` equals to `promocodeusage.Hooks(f(g(h())))`.
+func (c *PromoCodeUsageClient) Use(hooks ...Hook) {
+ c.hooks.PromoCodeUsage = append(c.hooks.PromoCodeUsage, hooks...)
+}
+
+// Intercept adds a list of query interceptors to the interceptors stack.
+// A call to `Intercept(f, g, h)` equals to `promocodeusage.Intercept(f(g(h())))`.
+func (c *PromoCodeUsageClient) Intercept(interceptors ...Interceptor) {
+ c.inters.PromoCodeUsage = append(c.inters.PromoCodeUsage, interceptors...)
+}
+
+// Create returns a builder for creating a PromoCodeUsage entity.
+func (c *PromoCodeUsageClient) Create() *PromoCodeUsageCreate {
+ mutation := newPromoCodeUsageMutation(c.config, OpCreate)
+ return &PromoCodeUsageCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// CreateBulk returns a builder for creating a bulk of PromoCodeUsage entities.
+func (c *PromoCodeUsageClient) CreateBulk(builders ...*PromoCodeUsageCreate) *PromoCodeUsageCreateBulk {
+ return &PromoCodeUsageCreateBulk{config: c.config, builders: builders}
+}
+
+// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
+// a builder and applies setFunc on it.
+func (c *PromoCodeUsageClient) MapCreateBulk(slice any, setFunc func(*PromoCodeUsageCreate, int)) *PromoCodeUsageCreateBulk {
+ rv := reflect.ValueOf(slice)
+ if rv.Kind() != reflect.Slice {
+ return &PromoCodeUsageCreateBulk{err: fmt.Errorf("calling to PromoCodeUsageClient.MapCreateBulk with wrong type %T, need slice", slice)}
+ }
+ builders := make([]*PromoCodeUsageCreate, rv.Len())
+ for i := 0; i < rv.Len(); i++ {
+ builders[i] = c.Create()
+ setFunc(builders[i], i)
+ }
+ return &PromoCodeUsageCreateBulk{config: c.config, builders: builders}
+}
+
+// Update returns an update builder for PromoCodeUsage.
+func (c *PromoCodeUsageClient) Update() *PromoCodeUsageUpdate {
+ mutation := newPromoCodeUsageMutation(c.config, OpUpdate)
+ return &PromoCodeUsageUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOne returns an update builder for the given entity.
+func (c *PromoCodeUsageClient) UpdateOne(_m *PromoCodeUsage) *PromoCodeUsageUpdateOne {
+ mutation := newPromoCodeUsageMutation(c.config, OpUpdateOne, withPromoCodeUsage(_m))
+ return &PromoCodeUsageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// UpdateOneID returns an update builder for the given id.
+func (c *PromoCodeUsageClient) UpdateOneID(id int64) *PromoCodeUsageUpdateOne {
+ mutation := newPromoCodeUsageMutation(c.config, OpUpdateOne, withPromoCodeUsageID(id))
+ return &PromoCodeUsageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// Delete returns a delete builder for PromoCodeUsage.
+func (c *PromoCodeUsageClient) Delete() *PromoCodeUsageDelete {
+ mutation := newPromoCodeUsageMutation(c.config, OpDelete)
+ return &PromoCodeUsageDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
+}
+
+// DeleteOne returns a builder for deleting the given entity.
+func (c *PromoCodeUsageClient) DeleteOne(_m *PromoCodeUsage) *PromoCodeUsageDeleteOne {
+ return c.DeleteOneID(_m.ID)
+}
+
+// DeleteOneID returns a builder for deleting the given entity by its id.
+func (c *PromoCodeUsageClient) DeleteOneID(id int64) *PromoCodeUsageDeleteOne {
+ builder := c.Delete().Where(promocodeusage.ID(id))
+ builder.mutation.id = &id
+ builder.mutation.op = OpDeleteOne
+ return &PromoCodeUsageDeleteOne{builder}
+}
+
+// Query returns a query builder for PromoCodeUsage.
+func (c *PromoCodeUsageClient) Query() *PromoCodeUsageQuery {
+ return &PromoCodeUsageQuery{
+ config: c.config,
+ ctx: &QueryContext{Type: TypePromoCodeUsage},
+ inters: c.Interceptors(),
+ }
+}
+
+// Get returns a PromoCodeUsage entity by its id.
+func (c *PromoCodeUsageClient) Get(ctx context.Context, id int64) (*PromoCodeUsage, error) {
+ return c.Query().Where(promocodeusage.ID(id)).Only(ctx)
+}
+
+// GetX is like Get, but panics if an error occurs.
+func (c *PromoCodeUsageClient) GetX(ctx context.Context, id int64) *PromoCodeUsage {
+ obj, err := c.Get(ctx, id)
+ if err != nil {
+ panic(err)
+ }
+ return obj
+}
+
+// QueryPromoCode queries the promo_code edge of a PromoCodeUsage.
+func (c *PromoCodeUsageClient) QueryPromoCode(_m *PromoCodeUsage) *PromoCodeQuery {
+ query := (&PromoCodeClient{config: c.config}).Query()
+ query.path = func(context.Context) (fromV *sql.Selector, _ error) {
+ id := _m.ID
+ step := sqlgraph.NewStep(
+ sqlgraph.From(promocodeusage.Table, promocodeusage.FieldID, id),
+ sqlgraph.To(promocode.Table, promocode.FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, promocodeusage.PromoCodeTable, promocodeusage.PromoCodeColumn),
+ )
+ fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
+// QueryUser queries the user edge of a PromoCodeUsage.
+func (c *PromoCodeUsageClient) QueryUser(_m *PromoCodeUsage) *UserQuery {
+ query := (&UserClient{config: c.config}).Query()
+ query.path = func(context.Context) (fromV *sql.Selector, _ error) {
+ id := _m.ID
+ step := sqlgraph.NewStep(
+ sqlgraph.From(promocodeusage.Table, promocodeusage.FieldID, id),
+ sqlgraph.To(user.Table, user.FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, promocodeusage.UserTable, promocodeusage.UserColumn),
+ )
+ fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
+// Hooks returns the client hooks.
+func (c *PromoCodeUsageClient) Hooks() []Hook {
+ return c.hooks.PromoCodeUsage
+}
+
+// Interceptors returns the client interceptors.
+func (c *PromoCodeUsageClient) Interceptors() []Interceptor {
+ return c.inters.PromoCodeUsage
+}
+
+func (c *PromoCodeUsageClient) mutate(ctx context.Context, m *PromoCodeUsageMutation) (Value, error) {
+ switch m.Op() {
+ case OpCreate:
+ return (&PromoCodeUsageCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpUpdate:
+ return (&PromoCodeUsageUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpUpdateOne:
+ return (&PromoCodeUsageUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
+ case OpDelete, OpDeleteOne:
+ return (&PromoCodeUsageDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
+ default:
+ return nil, fmt.Errorf("ent: unknown PromoCodeUsage mutation op: %q", m.Op())
+ }
+}
+
// ProxyClient is a client for the Proxy schema.
type ProxyClient struct {
config
@@ -1950,6 +2280,22 @@ func (c *UserClient) QueryAttributeValues(_m *User) *UserAttributeValueQuery {
return query
}
+// QueryPromoCodeUsages queries the promo_code_usages edge of a User.
+func (c *UserClient) QueryPromoCodeUsages(_m *User) *PromoCodeUsageQuery {
+ query := (&PromoCodeUsageClient{config: c.config}).Query()
+ query.path = func(context.Context) (fromV *sql.Selector, _ error) {
+ id := _m.ID
+ step := sqlgraph.NewStep(
+ sqlgraph.From(user.Table, user.FieldID, id),
+ sqlgraph.To(promocodeusage.Table, promocodeusage.FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, user.PromoCodeUsagesTable, user.PromoCodeUsagesColumn),
+ )
+ fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
+ return fromV, nil
+ }
+ return query
+}
+
// QueryUserAllowedGroups queries the user_allowed_groups edge of a User.
func (c *UserClient) QueryUserAllowedGroups(_m *User) *UserAllowedGroupQuery {
query := (&UserAllowedGroupClient{config: c.config}).Query()
@@ -2627,14 +2973,14 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription
// hooks and interceptors per client, for fast access.
type (
hooks struct {
- APIKey, Account, AccountGroup, Group, Proxy, RedeemCode, Setting, UsageLog,
- User, UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
- UserSubscription []ent.Hook
+ APIKey, Account, AccountGroup, Group, PromoCode, PromoCodeUsage, Proxy,
+ RedeemCode, Setting, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
+ UserAttributeValue, UserSubscription []ent.Hook
}
inters struct {
- APIKey, Account, AccountGroup, Group, Proxy, RedeemCode, Setting, UsageLog,
- User, UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
- UserSubscription []ent.Interceptor
+ APIKey, Account, AccountGroup, Group, PromoCode, PromoCodeUsage, Proxy,
+ RedeemCode, Setting, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
+ UserAttributeValue, UserSubscription []ent.Interceptor
}
)
diff --git a/backend/ent/ent.go b/backend/ent/ent.go
index 670ea0b2..410375a7 100644
--- a/backend/ent/ent.go
+++ b/backend/ent/ent.go
@@ -16,6 +16,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/setting"
@@ -89,6 +91,8 @@ func checkColumn(t, c string) error {
account.Table: account.ValidColumn,
accountgroup.Table: accountgroup.ValidColumn,
group.Table: group.ValidColumn,
+ promocode.Table: promocode.ValidColumn,
+ promocodeusage.Table: promocodeusage.ValidColumn,
proxy.Table: proxy.ValidColumn,
redeemcode.Table: redeemcode.ValidColumn,
setting.Table: setting.ValidColumn,
diff --git a/backend/ent/generate.go b/backend/ent/generate.go
index 22ab4a78..59843cec 100644
--- a/backend/ent/generate.go
+++ b/backend/ent/generate.go
@@ -2,4 +2,5 @@
package ent
// 启用 sql/execquery 以生成 ExecContext/QueryContext 的透传接口,便于事务内执行原生 SQL。
-//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert,intercept,sql/execquery --idtype int64 ./schema
+// 启用 sql/lock 以支持 FOR UPDATE 行锁。
+//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert,intercept,sql/execquery,sql/lock --idtype int64 ./schema
diff --git a/backend/ent/group_query.go b/backend/ent/group_query.go
index 3cc976cb..d4cc4f8d 100644
--- a/backend/ent/group_query.go
+++ b/backend/ent/group_query.go
@@ -9,6 +9,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -39,6 +40,7 @@ type GroupQuery struct {
withAllowedUsers *UserQuery
withAccountGroups *AccountGroupQuery
withUserAllowedGroups *UserAllowedGroupQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -643,6 +645,9 @@ func (_q *GroupQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Group,
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -1025,6 +1030,9 @@ func (_q *GroupQuery) loadUserAllowedGroups(ctx context.Context, query *UserAllo
func (_q *GroupQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -1087,6 +1095,9 @@ func (_q *GroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -1104,6 +1115,32 @@ func (_q *GroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *GroupQuery) ForUpdate(opts ...sql.LockOption) *GroupQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *GroupQuery) ForShare(opts ...sql.LockOption) *GroupQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// GroupGroupBy is the group-by builder for Group entities.
type GroupGroupBy struct {
selector
diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go
index e82b00f9..532b0d2c 100644
--- a/backend/ent/hook/hook.go
+++ b/backend/ent/hook/hook.go
@@ -57,6 +57,30 @@ func (f GroupFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.GroupMutation", m)
}
+// The PromoCodeFunc type is an adapter to allow the use of ordinary
+// function as PromoCode mutator.
+type PromoCodeFunc func(context.Context, *ent.PromoCodeMutation) (ent.Value, error)
+
+// Mutate calls f(ctx, m).
+func (f PromoCodeFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
+ if mv, ok := m.(*ent.PromoCodeMutation); ok {
+ return f(ctx, mv)
+ }
+ return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PromoCodeMutation", m)
+}
+
+// The PromoCodeUsageFunc type is an adapter to allow the use of ordinary
+// function as PromoCodeUsage mutator.
+type PromoCodeUsageFunc func(context.Context, *ent.PromoCodeUsageMutation) (ent.Value, error)
+
+// Mutate calls f(ctx, m).
+func (f PromoCodeUsageFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
+ if mv, ok := m.(*ent.PromoCodeUsageMutation); ok {
+ return f(ctx, mv)
+ }
+ return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PromoCodeUsageMutation", m)
+}
+
// The ProxyFunc type is an adapter to allow the use of ordinary
// function as Proxy mutator.
type ProxyFunc func(context.Context, *ent.ProxyMutation) (ent.Value, error)
diff --git a/backend/ent/intercept/intercept.go b/backend/ent/intercept/intercept.go
index 6add6fed..765d39b4 100644
--- a/backend/ent/intercept/intercept.go
+++ b/backend/ent/intercept/intercept.go
@@ -13,6 +13,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/setting"
@@ -188,6 +190,60 @@ func (f TraverseGroup) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.GroupQuery", q)
}
+// The PromoCodeFunc type is an adapter to allow the use of ordinary function as a Querier.
+type PromoCodeFunc func(context.Context, *ent.PromoCodeQuery) (ent.Value, error)
+
+// Query calls f(ctx, q).
+func (f PromoCodeFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
+ if q, ok := q.(*ent.PromoCodeQuery); ok {
+ return f(ctx, q)
+ }
+ return nil, fmt.Errorf("unexpected query type %T. expect *ent.PromoCodeQuery", q)
+}
+
+// The TraversePromoCode type is an adapter to allow the use of ordinary function as Traverser.
+type TraversePromoCode func(context.Context, *ent.PromoCodeQuery) error
+
+// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
+func (f TraversePromoCode) Intercept(next ent.Querier) ent.Querier {
+ return next
+}
+
+// Traverse calls f(ctx, q).
+func (f TraversePromoCode) Traverse(ctx context.Context, q ent.Query) error {
+ if q, ok := q.(*ent.PromoCodeQuery); ok {
+ return f(ctx, q)
+ }
+ return fmt.Errorf("unexpected query type %T. expect *ent.PromoCodeQuery", q)
+}
+
+// The PromoCodeUsageFunc type is an adapter to allow the use of ordinary function as a Querier.
+type PromoCodeUsageFunc func(context.Context, *ent.PromoCodeUsageQuery) (ent.Value, error)
+
+// Query calls f(ctx, q).
+func (f PromoCodeUsageFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
+ if q, ok := q.(*ent.PromoCodeUsageQuery); ok {
+ return f(ctx, q)
+ }
+ return nil, fmt.Errorf("unexpected query type %T. expect *ent.PromoCodeUsageQuery", q)
+}
+
+// The TraversePromoCodeUsage type is an adapter to allow the use of ordinary function as Traverser.
+type TraversePromoCodeUsage func(context.Context, *ent.PromoCodeUsageQuery) error
+
+// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
+func (f TraversePromoCodeUsage) Intercept(next ent.Querier) ent.Querier {
+ return next
+}
+
+// Traverse calls f(ctx, q).
+func (f TraversePromoCodeUsage) Traverse(ctx context.Context, q ent.Query) error {
+ if q, ok := q.(*ent.PromoCodeUsageQuery); ok {
+ return f(ctx, q)
+ }
+ return fmt.Errorf("unexpected query type %T. expect *ent.PromoCodeUsageQuery", q)
+}
+
// The ProxyFunc type is an adapter to allow the use of ordinary function as a Querier.
type ProxyFunc func(context.Context, *ent.ProxyQuery) (ent.Value, error)
@@ -442,6 +498,10 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.AccountGroupQuery, predicate.AccountGroup, accountgroup.OrderOption]{typ: ent.TypeAccountGroup, tq: q}, nil
case *ent.GroupQuery:
return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil
+ case *ent.PromoCodeQuery:
+ return &query[*ent.PromoCodeQuery, predicate.PromoCode, promocode.OrderOption]{typ: ent.TypePromoCode, tq: q}, nil
+ case *ent.PromoCodeUsageQuery:
+ return &query[*ent.PromoCodeUsageQuery, predicate.PromoCodeUsage, promocodeusage.OrderOption]{typ: ent.TypePromoCodeUsage, tq: q}, nil
case *ent.ProxyQuery:
return &query[*ent.ProxyQuery, predicate.Proxy, proxy.OrderOption]{typ: ent.TypeProxy, tq: q}, nil
case *ent.RedeemCodeQuery:
diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go
index fdde0cd1..41cd8b01 100644
--- a/backend/ent/migrate/schema.go
+++ b/backend/ent/migrate/schema.go
@@ -259,6 +259,82 @@ var (
},
},
}
+ // PromoCodesColumns holds the columns for the "promo_codes" table.
+ PromoCodesColumns = []*schema.Column{
+ {Name: "id", Type: field.TypeInt64, Increment: true},
+ {Name: "code", Type: field.TypeString, Unique: true, Size: 32},
+ {Name: "bonus_amount", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
+ {Name: "max_uses", Type: field.TypeInt, Default: 0},
+ {Name: "used_count", Type: field.TypeInt, Default: 0},
+ {Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
+ {Name: "expires_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
+ {Name: "notes", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
+ {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
+ {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
+ }
+ // PromoCodesTable holds the schema information for the "promo_codes" table.
+ PromoCodesTable = &schema.Table{
+ Name: "promo_codes",
+ Columns: PromoCodesColumns,
+ PrimaryKey: []*schema.Column{PromoCodesColumns[0]},
+ Indexes: []*schema.Index{
+ {
+ Name: "promocode_status",
+ Unique: false,
+ Columns: []*schema.Column{PromoCodesColumns[5]},
+ },
+ {
+ Name: "promocode_expires_at",
+ Unique: false,
+ Columns: []*schema.Column{PromoCodesColumns[6]},
+ },
+ },
+ }
+ // PromoCodeUsagesColumns holds the columns for the "promo_code_usages" table.
+ PromoCodeUsagesColumns = []*schema.Column{
+ {Name: "id", Type: field.TypeInt64, Increment: true},
+ {Name: "bonus_amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
+ {Name: "used_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
+ {Name: "promo_code_id", Type: field.TypeInt64},
+ {Name: "user_id", Type: field.TypeInt64},
+ }
+ // PromoCodeUsagesTable holds the schema information for the "promo_code_usages" table.
+ PromoCodeUsagesTable = &schema.Table{
+ Name: "promo_code_usages",
+ Columns: PromoCodeUsagesColumns,
+ PrimaryKey: []*schema.Column{PromoCodeUsagesColumns[0]},
+ ForeignKeys: []*schema.ForeignKey{
+ {
+ Symbol: "promo_code_usages_promo_codes_usage_records",
+ Columns: []*schema.Column{PromoCodeUsagesColumns[3]},
+ RefColumns: []*schema.Column{PromoCodesColumns[0]},
+ OnDelete: schema.NoAction,
+ },
+ {
+ Symbol: "promo_code_usages_users_promo_code_usages",
+ Columns: []*schema.Column{PromoCodeUsagesColumns[4]},
+ RefColumns: []*schema.Column{UsersColumns[0]},
+ OnDelete: schema.NoAction,
+ },
+ },
+ Indexes: []*schema.Index{
+ {
+ Name: "promocodeusage_promo_code_id",
+ Unique: false,
+ Columns: []*schema.Column{PromoCodeUsagesColumns[3]},
+ },
+ {
+ Name: "promocodeusage_user_id",
+ Unique: false,
+ Columns: []*schema.Column{PromoCodeUsagesColumns[4]},
+ },
+ {
+ Name: "promocodeusage_promo_code_id_user_id",
+ Unique: true,
+ Columns: []*schema.Column{PromoCodeUsagesColumns[3], PromoCodeUsagesColumns[4]},
+ },
+ },
+ }
// ProxiesColumns holds the columns for the "proxies" table.
ProxiesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -720,6 +796,8 @@ var (
AccountsTable,
AccountGroupsTable,
GroupsTable,
+ PromoCodesTable,
+ PromoCodeUsagesTable,
ProxiesTable,
RedeemCodesTable,
SettingsTable,
@@ -750,6 +828,14 @@ func init() {
GroupsTable.Annotation = &entsql.Annotation{
Table: "groups",
}
+ PromoCodesTable.Annotation = &entsql.Annotation{
+ Table: "promo_codes",
+ }
+ PromoCodeUsagesTable.ForeignKeys[0].RefTable = PromoCodesTable
+ PromoCodeUsagesTable.ForeignKeys[1].RefTable = UsersTable
+ PromoCodeUsagesTable.Annotation = &entsql.Annotation{
+ Table: "promo_code_usages",
+ }
ProxiesTable.Annotation = &entsql.Annotation{
Table: "proxies",
}
diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go
index 09801d4b..732abd1c 100644
--- a/backend/ent/mutation.go
+++ b/backend/ent/mutation.go
@@ -16,6 +16,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/setting"
@@ -40,6 +42,8 @@ const (
TypeAccount = "Account"
TypeAccountGroup = "AccountGroup"
TypeGroup = "Group"
+ TypePromoCode = "PromoCode"
+ TypePromoCodeUsage = "PromoCodeUsage"
TypeProxy = "Proxy"
TypeRedeemCode = "RedeemCode"
TypeSetting = "Setting"
@@ -6026,6 +6030,1624 @@ func (m *GroupMutation) ResetEdge(name string) error {
return fmt.Errorf("unknown Group edge %s", name)
}
+// PromoCodeMutation represents an operation that mutates the PromoCode nodes in the graph.
+type PromoCodeMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ code *string
+ bonus_amount *float64
+ addbonus_amount *float64
+ max_uses *int
+ addmax_uses *int
+ used_count *int
+ addused_count *int
+ status *string
+ expires_at *time.Time
+ notes *string
+ created_at *time.Time
+ updated_at *time.Time
+ clearedFields map[string]struct{}
+ usage_records map[int64]struct{}
+ removedusage_records map[int64]struct{}
+ clearedusage_records bool
+ done bool
+ oldValue func(context.Context) (*PromoCode, error)
+ predicates []predicate.PromoCode
+}
+
+var _ ent.Mutation = (*PromoCodeMutation)(nil)
+
+// promocodeOption allows management of the mutation configuration using functional options.
+type promocodeOption func(*PromoCodeMutation)
+
+// newPromoCodeMutation creates new mutation for the PromoCode entity.
+func newPromoCodeMutation(c config, op Op, opts ...promocodeOption) *PromoCodeMutation {
+ m := &PromoCodeMutation{
+ config: c,
+ op: op,
+ typ: TypePromoCode,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withPromoCodeID sets the ID field of the mutation.
+func withPromoCodeID(id int64) promocodeOption {
+ return func(m *PromoCodeMutation) {
+ var (
+ err error
+ once sync.Once
+ value *PromoCode
+ )
+ m.oldValue = func(ctx context.Context) (*PromoCode, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().PromoCode.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withPromoCode sets the old PromoCode of the mutation.
+func withPromoCode(node *PromoCode) promocodeOption {
+ return func(m *PromoCodeMutation) {
+ m.oldValue = func(context.Context) (*PromoCode, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m PromoCodeMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m PromoCodeMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *PromoCodeMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *PromoCodeMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().PromoCode.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetCode sets the "code" field.
+func (m *PromoCodeMutation) SetCode(s string) {
+ m.code = &s
+}
+
+// Code returns the value of the "code" field in the mutation.
+func (m *PromoCodeMutation) Code() (r string, exists bool) {
+ v := m.code
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCode returns the old "code" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldCode(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCode is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCode requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCode: %w", err)
+ }
+ return oldValue.Code, nil
+}
+
+// ResetCode resets all changes to the "code" field.
+func (m *PromoCodeMutation) ResetCode() {
+ m.code = nil
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (m *PromoCodeMutation) SetBonusAmount(f float64) {
+ m.bonus_amount = &f
+ m.addbonus_amount = nil
+}
+
+// BonusAmount returns the value of the "bonus_amount" field in the mutation.
+func (m *PromoCodeMutation) BonusAmount() (r float64, exists bool) {
+ v := m.bonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldBonusAmount returns the old "bonus_amount" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldBonusAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldBonusAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldBonusAmount: %w", err)
+ }
+ return oldValue.BonusAmount, nil
+}
+
+// AddBonusAmount adds f to the "bonus_amount" field.
+func (m *PromoCodeMutation) AddBonusAmount(f float64) {
+ if m.addbonus_amount != nil {
+ *m.addbonus_amount += f
+ } else {
+ m.addbonus_amount = &f
+ }
+}
+
+// AddedBonusAmount returns the value that was added to the "bonus_amount" field in this mutation.
+func (m *PromoCodeMutation) AddedBonusAmount() (r float64, exists bool) {
+ v := m.addbonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetBonusAmount resets all changes to the "bonus_amount" field.
+func (m *PromoCodeMutation) ResetBonusAmount() {
+ m.bonus_amount = nil
+ m.addbonus_amount = nil
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (m *PromoCodeMutation) SetMaxUses(i int) {
+ m.max_uses = &i
+ m.addmax_uses = nil
+}
+
+// MaxUses returns the value of the "max_uses" field in the mutation.
+func (m *PromoCodeMutation) MaxUses() (r int, exists bool) {
+ v := m.max_uses
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldMaxUses returns the old "max_uses" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldMaxUses(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldMaxUses is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldMaxUses requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldMaxUses: %w", err)
+ }
+ return oldValue.MaxUses, nil
+}
+
+// AddMaxUses adds i to the "max_uses" field.
+func (m *PromoCodeMutation) AddMaxUses(i int) {
+ if m.addmax_uses != nil {
+ *m.addmax_uses += i
+ } else {
+ m.addmax_uses = &i
+ }
+}
+
+// AddedMaxUses returns the value that was added to the "max_uses" field in this mutation.
+func (m *PromoCodeMutation) AddedMaxUses() (r int, exists bool) {
+ v := m.addmax_uses
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetMaxUses resets all changes to the "max_uses" field.
+func (m *PromoCodeMutation) ResetMaxUses() {
+ m.max_uses = nil
+ m.addmax_uses = nil
+}
+
+// SetUsedCount sets the "used_count" field.
+func (m *PromoCodeMutation) SetUsedCount(i int) {
+ m.used_count = &i
+ m.addused_count = nil
+}
+
+// UsedCount returns the value of the "used_count" field in the mutation.
+func (m *PromoCodeMutation) UsedCount() (r int, exists bool) {
+ v := m.used_count
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUsedCount returns the old "used_count" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldUsedCount(ctx context.Context) (v int, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUsedCount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUsedCount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUsedCount: %w", err)
+ }
+ return oldValue.UsedCount, nil
+}
+
+// AddUsedCount adds i to the "used_count" field.
+func (m *PromoCodeMutation) AddUsedCount(i int) {
+ if m.addused_count != nil {
+ *m.addused_count += i
+ } else {
+ m.addused_count = &i
+ }
+}
+
+// AddedUsedCount returns the value that was added to the "used_count" field in this mutation.
+func (m *PromoCodeMutation) AddedUsedCount() (r int, exists bool) {
+ v := m.addused_count
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetUsedCount resets all changes to the "used_count" field.
+func (m *PromoCodeMutation) ResetUsedCount() {
+ m.used_count = nil
+ m.addused_count = nil
+}
+
+// SetStatus sets the "status" field.
+func (m *PromoCodeMutation) SetStatus(s string) {
+ m.status = &s
+}
+
+// Status returns the value of the "status" field in the mutation.
+func (m *PromoCodeMutation) Status() (r string, exists bool) {
+ v := m.status
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldStatus returns the old "status" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldStatus(ctx context.Context) (v string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldStatus is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldStatus requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldStatus: %w", err)
+ }
+ return oldValue.Status, nil
+}
+
+// ResetStatus resets all changes to the "status" field.
+func (m *PromoCodeMutation) ResetStatus() {
+ m.status = nil
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (m *PromoCodeMutation) SetExpiresAt(t time.Time) {
+ m.expires_at = &t
+}
+
+// ExpiresAt returns the value of the "expires_at" field in the mutation.
+func (m *PromoCodeMutation) ExpiresAt() (r time.Time, exists bool) {
+ v := m.expires_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldExpiresAt returns the old "expires_at" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldExpiresAt(ctx context.Context) (v *time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldExpiresAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err)
+ }
+ return oldValue.ExpiresAt, nil
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (m *PromoCodeMutation) ClearExpiresAt() {
+ m.expires_at = nil
+ m.clearedFields[promocode.FieldExpiresAt] = struct{}{}
+}
+
+// ExpiresAtCleared returns if the "expires_at" field was cleared in this mutation.
+func (m *PromoCodeMutation) ExpiresAtCleared() bool {
+ _, ok := m.clearedFields[promocode.FieldExpiresAt]
+ return ok
+}
+
+// ResetExpiresAt resets all changes to the "expires_at" field.
+func (m *PromoCodeMutation) ResetExpiresAt() {
+ m.expires_at = nil
+ delete(m.clearedFields, promocode.FieldExpiresAt)
+}
+
+// SetNotes sets the "notes" field.
+func (m *PromoCodeMutation) SetNotes(s string) {
+ m.notes = &s
+}
+
+// Notes returns the value of the "notes" field in the mutation.
+func (m *PromoCodeMutation) Notes() (r string, exists bool) {
+ v := m.notes
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldNotes returns the old "notes" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldNotes(ctx context.Context) (v *string, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldNotes is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldNotes requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldNotes: %w", err)
+ }
+ return oldValue.Notes, nil
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (m *PromoCodeMutation) ClearNotes() {
+ m.notes = nil
+ m.clearedFields[promocode.FieldNotes] = struct{}{}
+}
+
+// NotesCleared returns if the "notes" field was cleared in this mutation.
+func (m *PromoCodeMutation) NotesCleared() bool {
+ _, ok := m.clearedFields[promocode.FieldNotes]
+ return ok
+}
+
+// ResetNotes resets all changes to the "notes" field.
+func (m *PromoCodeMutation) ResetNotes() {
+ m.notes = nil
+ delete(m.clearedFields, promocode.FieldNotes)
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (m *PromoCodeMutation) SetCreatedAt(t time.Time) {
+ m.created_at = &t
+}
+
+// CreatedAt returns the value of the "created_at" field in the mutation.
+func (m *PromoCodeMutation) CreatedAt() (r time.Time, exists bool) {
+ v := m.created_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldCreatedAt returns the old "created_at" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldCreatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
+ }
+ return oldValue.CreatedAt, nil
+}
+
+// ResetCreatedAt resets all changes to the "created_at" field.
+func (m *PromoCodeMutation) ResetCreatedAt() {
+ m.created_at = nil
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (m *PromoCodeMutation) SetUpdatedAt(t time.Time) {
+ m.updated_at = &t
+}
+
+// UpdatedAt returns the value of the "updated_at" field in the mutation.
+func (m *PromoCodeMutation) UpdatedAt() (r time.Time, exists bool) {
+ v := m.updated_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUpdatedAt returns the old "updated_at" field's value of the PromoCode entity.
+// If the PromoCode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
+ }
+ return oldValue.UpdatedAt, nil
+}
+
+// ResetUpdatedAt resets all changes to the "updated_at" field.
+func (m *PromoCodeMutation) ResetUpdatedAt() {
+ m.updated_at = nil
+}
+
+// AddUsageRecordIDs adds the "usage_records" edge to the PromoCodeUsage entity by ids.
+func (m *PromoCodeMutation) AddUsageRecordIDs(ids ...int64) {
+ if m.usage_records == nil {
+ m.usage_records = make(map[int64]struct{})
+ }
+ for i := range ids {
+ m.usage_records[ids[i]] = struct{}{}
+ }
+}
+
+// ClearUsageRecords clears the "usage_records" edge to the PromoCodeUsage entity.
+func (m *PromoCodeMutation) ClearUsageRecords() {
+ m.clearedusage_records = true
+}
+
+// UsageRecordsCleared reports if the "usage_records" edge to the PromoCodeUsage entity was cleared.
+func (m *PromoCodeMutation) UsageRecordsCleared() bool {
+ return m.clearedusage_records
+}
+
+// RemoveUsageRecordIDs removes the "usage_records" edge to the PromoCodeUsage entity by IDs.
+func (m *PromoCodeMutation) RemoveUsageRecordIDs(ids ...int64) {
+ if m.removedusage_records == nil {
+ m.removedusage_records = make(map[int64]struct{})
+ }
+ for i := range ids {
+ delete(m.usage_records, ids[i])
+ m.removedusage_records[ids[i]] = struct{}{}
+ }
+}
+
+// RemovedUsageRecords returns the removed IDs of the "usage_records" edge to the PromoCodeUsage entity.
+func (m *PromoCodeMutation) RemovedUsageRecordsIDs() (ids []int64) {
+ for id := range m.removedusage_records {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// UsageRecordsIDs returns the "usage_records" edge IDs in the mutation.
+func (m *PromoCodeMutation) UsageRecordsIDs() (ids []int64) {
+ for id := range m.usage_records {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// ResetUsageRecords resets all changes to the "usage_records" edge.
+func (m *PromoCodeMutation) ResetUsageRecords() {
+ m.usage_records = nil
+ m.clearedusage_records = false
+ m.removedusage_records = nil
+}
+
+// Where appends a list predicates to the PromoCodeMutation builder.
+func (m *PromoCodeMutation) Where(ps ...predicate.PromoCode) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the PromoCodeMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PromoCodeMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PromoCode, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *PromoCodeMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *PromoCodeMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (PromoCode).
+func (m *PromoCodeMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PromoCodeMutation) Fields() []string {
+ fields := make([]string, 0, 9)
+ if m.code != nil {
+ fields = append(fields, promocode.FieldCode)
+ }
+ if m.bonus_amount != nil {
+ fields = append(fields, promocode.FieldBonusAmount)
+ }
+ if m.max_uses != nil {
+ fields = append(fields, promocode.FieldMaxUses)
+ }
+ if m.used_count != nil {
+ fields = append(fields, promocode.FieldUsedCount)
+ }
+ if m.status != nil {
+ fields = append(fields, promocode.FieldStatus)
+ }
+ if m.expires_at != nil {
+ fields = append(fields, promocode.FieldExpiresAt)
+ }
+ if m.notes != nil {
+ fields = append(fields, promocode.FieldNotes)
+ }
+ if m.created_at != nil {
+ fields = append(fields, promocode.FieldCreatedAt)
+ }
+ if m.updated_at != nil {
+ fields = append(fields, promocode.FieldUpdatedAt)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PromoCodeMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case promocode.FieldCode:
+ return m.Code()
+ case promocode.FieldBonusAmount:
+ return m.BonusAmount()
+ case promocode.FieldMaxUses:
+ return m.MaxUses()
+ case promocode.FieldUsedCount:
+ return m.UsedCount()
+ case promocode.FieldStatus:
+ return m.Status()
+ case promocode.FieldExpiresAt:
+ return m.ExpiresAt()
+ case promocode.FieldNotes:
+ return m.Notes()
+ case promocode.FieldCreatedAt:
+ return m.CreatedAt()
+ case promocode.FieldUpdatedAt:
+ return m.UpdatedAt()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PromoCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case promocode.FieldCode:
+ return m.OldCode(ctx)
+ case promocode.FieldBonusAmount:
+ return m.OldBonusAmount(ctx)
+ case promocode.FieldMaxUses:
+ return m.OldMaxUses(ctx)
+ case promocode.FieldUsedCount:
+ return m.OldUsedCount(ctx)
+ case promocode.FieldStatus:
+ return m.OldStatus(ctx)
+ case promocode.FieldExpiresAt:
+ return m.OldExpiresAt(ctx)
+ case promocode.FieldNotes:
+ return m.OldNotes(ctx)
+ case promocode.FieldCreatedAt:
+ return m.OldCreatedAt(ctx)
+ case promocode.FieldUpdatedAt:
+ return m.OldUpdatedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PromoCode field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case promocode.FieldCode:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCode(v)
+ return nil
+ case promocode.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetBonusAmount(v)
+ return nil
+ case promocode.FieldMaxUses:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetMaxUses(v)
+ return nil
+ case promocode.FieldUsedCount:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUsedCount(v)
+ return nil
+ case promocode.FieldStatus:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetStatus(v)
+ return nil
+ case promocode.FieldExpiresAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetExpiresAt(v)
+ return nil
+ case promocode.FieldNotes:
+ v, ok := value.(string)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetNotes(v)
+ return nil
+ case promocode.FieldCreatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetCreatedAt(v)
+ return nil
+ case promocode.FieldUpdatedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUpdatedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PromoCodeMutation) AddedFields() []string {
+ var fields []string
+ if m.addbonus_amount != nil {
+ fields = append(fields, promocode.FieldBonusAmount)
+ }
+ if m.addmax_uses != nil {
+ fields = append(fields, promocode.FieldMaxUses)
+ }
+ if m.addused_count != nil {
+ fields = append(fields, promocode.FieldUsedCount)
+ }
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PromoCodeMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case promocode.FieldBonusAmount:
+ return m.AddedBonusAmount()
+ case promocode.FieldMaxUses:
+ return m.AddedMaxUses()
+ case promocode.FieldUsedCount:
+ return m.AddedUsedCount()
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ case promocode.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddBonusAmount(v)
+ return nil
+ case promocode.FieldMaxUses:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddMaxUses(v)
+ return nil
+ case promocode.FieldUsedCount:
+ v, ok := value.(int)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddUsedCount(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PromoCodeMutation) ClearedFields() []string {
+ var fields []string
+ if m.FieldCleared(promocode.FieldExpiresAt) {
+ fields = append(fields, promocode.FieldExpiresAt)
+ }
+ if m.FieldCleared(promocode.FieldNotes) {
+ fields = append(fields, promocode.FieldNotes)
+ }
+ return fields
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PromoCodeMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PromoCodeMutation) ClearField(name string) error {
+ switch name {
+ case promocode.FieldExpiresAt:
+ m.ClearExpiresAt()
+ return nil
+ case promocode.FieldNotes:
+ m.ClearNotes()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PromoCodeMutation) ResetField(name string) error {
+ switch name {
+ case promocode.FieldCode:
+ m.ResetCode()
+ return nil
+ case promocode.FieldBonusAmount:
+ m.ResetBonusAmount()
+ return nil
+ case promocode.FieldMaxUses:
+ m.ResetMaxUses()
+ return nil
+ case promocode.FieldUsedCount:
+ m.ResetUsedCount()
+ return nil
+ case promocode.FieldStatus:
+ m.ResetStatus()
+ return nil
+ case promocode.FieldExpiresAt:
+ m.ResetExpiresAt()
+ return nil
+ case promocode.FieldNotes:
+ m.ResetNotes()
+ return nil
+ case promocode.FieldCreatedAt:
+ m.ResetCreatedAt()
+ return nil
+ case promocode.FieldUpdatedAt:
+ m.ResetUpdatedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PromoCodeMutation) AddedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.usage_records != nil {
+ edges = append(edges, promocode.EdgeUsageRecords)
+ }
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PromoCodeMutation) AddedIDs(name string) []ent.Value {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ ids := make([]ent.Value, 0, len(m.usage_records))
+ for id := range m.usage_records {
+ ids = append(ids, id)
+ }
+ return ids
+ }
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PromoCodeMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.removedusage_records != nil {
+ edges = append(edges, promocode.EdgeUsageRecords)
+ }
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PromoCodeMutation) RemovedIDs(name string) []ent.Value {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ ids := make([]ent.Value, 0, len(m.removedusage_records))
+ for id := range m.removedusage_records {
+ ids = append(ids, id)
+ }
+ return ids
+ }
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PromoCodeMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 1)
+ if m.clearedusage_records {
+ edges = append(edges, promocode.EdgeUsageRecords)
+ }
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PromoCodeMutation) EdgeCleared(name string) bool {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ return m.clearedusage_records
+ }
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PromoCodeMutation) ClearEdge(name string) error {
+ switch name {
+ }
+ return fmt.Errorf("unknown PromoCode unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PromoCodeMutation) ResetEdge(name string) error {
+ switch name {
+ case promocode.EdgeUsageRecords:
+ m.ResetUsageRecords()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCode edge %s", name)
+}
+
+// PromoCodeUsageMutation represents an operation that mutates the PromoCodeUsage nodes in the graph.
+type PromoCodeUsageMutation struct {
+ config
+ op Op
+ typ string
+ id *int64
+ bonus_amount *float64
+ addbonus_amount *float64
+ used_at *time.Time
+ clearedFields map[string]struct{}
+ promo_code *int64
+ clearedpromo_code bool
+ user *int64
+ cleareduser bool
+ done bool
+ oldValue func(context.Context) (*PromoCodeUsage, error)
+ predicates []predicate.PromoCodeUsage
+}
+
+var _ ent.Mutation = (*PromoCodeUsageMutation)(nil)
+
+// promocodeusageOption allows management of the mutation configuration using functional options.
+type promocodeusageOption func(*PromoCodeUsageMutation)
+
+// newPromoCodeUsageMutation creates new mutation for the PromoCodeUsage entity.
+func newPromoCodeUsageMutation(c config, op Op, opts ...promocodeusageOption) *PromoCodeUsageMutation {
+ m := &PromoCodeUsageMutation{
+ config: c,
+ op: op,
+ typ: TypePromoCodeUsage,
+ clearedFields: make(map[string]struct{}),
+ }
+ for _, opt := range opts {
+ opt(m)
+ }
+ return m
+}
+
+// withPromoCodeUsageID sets the ID field of the mutation.
+func withPromoCodeUsageID(id int64) promocodeusageOption {
+ return func(m *PromoCodeUsageMutation) {
+ var (
+ err error
+ once sync.Once
+ value *PromoCodeUsage
+ )
+ m.oldValue = func(ctx context.Context) (*PromoCodeUsage, error) {
+ once.Do(func() {
+ if m.done {
+ err = errors.New("querying old values post mutation is not allowed")
+ } else {
+ value, err = m.Client().PromoCodeUsage.Get(ctx, id)
+ }
+ })
+ return value, err
+ }
+ m.id = &id
+ }
+}
+
+// withPromoCodeUsage sets the old PromoCodeUsage of the mutation.
+func withPromoCodeUsage(node *PromoCodeUsage) promocodeusageOption {
+ return func(m *PromoCodeUsageMutation) {
+ m.oldValue = func(context.Context) (*PromoCodeUsage, error) {
+ return node, nil
+ }
+ m.id = &node.ID
+ }
+}
+
+// Client returns a new `ent.Client` from the mutation. If the mutation was
+// executed in a transaction (ent.Tx), a transactional client is returned.
+func (m PromoCodeUsageMutation) Client() *Client {
+ client := &Client{config: m.config}
+ client.init()
+ return client
+}
+
+// Tx returns an `ent.Tx` for mutations that were executed in transactions;
+// it returns an error otherwise.
+func (m PromoCodeUsageMutation) Tx() (*Tx, error) {
+ if _, ok := m.driver.(*txDriver); !ok {
+ return nil, errors.New("ent: mutation is not running in a transaction")
+ }
+ tx := &Tx{config: m.config}
+ tx.init()
+ return tx, nil
+}
+
+// ID returns the ID value in the mutation. Note that the ID is only available
+// if it was provided to the builder or after it was returned from the database.
+func (m *PromoCodeUsageMutation) ID() (id int64, exists bool) {
+ if m.id == nil {
+ return
+ }
+ return *m.id, true
+}
+
+// IDs queries the database and returns the entity ids that match the mutation's predicate.
+// That means, if the mutation is applied within a transaction with an isolation level such
+// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
+// or updated by the mutation.
+func (m *PromoCodeUsageMutation) IDs(ctx context.Context) ([]int64, error) {
+ switch {
+ case m.op.Is(OpUpdateOne | OpDeleteOne):
+ id, exists := m.ID()
+ if exists {
+ return []int64{id}, nil
+ }
+ fallthrough
+ case m.op.Is(OpUpdate | OpDelete):
+ return m.Client().PromoCodeUsage.Query().Where(m.predicates...).IDs(ctx)
+ default:
+ return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
+ }
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (m *PromoCodeUsageMutation) SetPromoCodeID(i int64) {
+ m.promo_code = &i
+}
+
+// PromoCodeID returns the value of the "promo_code_id" field in the mutation.
+func (m *PromoCodeUsageMutation) PromoCodeID() (r int64, exists bool) {
+ v := m.promo_code
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldPromoCodeID returns the old "promo_code_id" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldPromoCodeID(ctx context.Context) (v int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldPromoCodeID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldPromoCodeID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldPromoCodeID: %w", err)
+ }
+ return oldValue.PromoCodeID, nil
+}
+
+// ResetPromoCodeID resets all changes to the "promo_code_id" field.
+func (m *PromoCodeUsageMutation) ResetPromoCodeID() {
+ m.promo_code = nil
+}
+
+// SetUserID sets the "user_id" field.
+func (m *PromoCodeUsageMutation) SetUserID(i int64) {
+ m.user = &i
+}
+
+// UserID returns the value of the "user_id" field in the mutation.
+func (m *PromoCodeUsageMutation) UserID() (r int64, exists bool) {
+ v := m.user
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUserID returns the old "user_id" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldUserID(ctx context.Context) (v int64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUserID is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUserID requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUserID: %w", err)
+ }
+ return oldValue.UserID, nil
+}
+
+// ResetUserID resets all changes to the "user_id" field.
+func (m *PromoCodeUsageMutation) ResetUserID() {
+ m.user = nil
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (m *PromoCodeUsageMutation) SetBonusAmount(f float64) {
+ m.bonus_amount = &f
+ m.addbonus_amount = nil
+}
+
+// BonusAmount returns the value of the "bonus_amount" field in the mutation.
+func (m *PromoCodeUsageMutation) BonusAmount() (r float64, exists bool) {
+ v := m.bonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldBonusAmount returns the old "bonus_amount" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldBonusAmount(ctx context.Context) (v float64, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldBonusAmount requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldBonusAmount: %w", err)
+ }
+ return oldValue.BonusAmount, nil
+}
+
+// AddBonusAmount adds f to the "bonus_amount" field.
+func (m *PromoCodeUsageMutation) AddBonusAmount(f float64) {
+ if m.addbonus_amount != nil {
+ *m.addbonus_amount += f
+ } else {
+ m.addbonus_amount = &f
+ }
+}
+
+// AddedBonusAmount returns the value that was added to the "bonus_amount" field in this mutation.
+func (m *PromoCodeUsageMutation) AddedBonusAmount() (r float64, exists bool) {
+ v := m.addbonus_amount
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// ResetBonusAmount resets all changes to the "bonus_amount" field.
+func (m *PromoCodeUsageMutation) ResetBonusAmount() {
+ m.bonus_amount = nil
+ m.addbonus_amount = nil
+}
+
+// SetUsedAt sets the "used_at" field.
+func (m *PromoCodeUsageMutation) SetUsedAt(t time.Time) {
+ m.used_at = &t
+}
+
+// UsedAt returns the value of the "used_at" field in the mutation.
+func (m *PromoCodeUsageMutation) UsedAt() (r time.Time, exists bool) {
+ v := m.used_at
+ if v == nil {
+ return
+ }
+ return *v, true
+}
+
+// OldUsedAt returns the old "used_at" field's value of the PromoCodeUsage entity.
+// If the PromoCodeUsage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *PromoCodeUsageMutation) OldUsedAt(ctx context.Context) (v time.Time, err error) {
+ if !m.op.Is(OpUpdateOne) {
+ return v, errors.New("OldUsedAt is only allowed on UpdateOne operations")
+ }
+ if m.id == nil || m.oldValue == nil {
+ return v, errors.New("OldUsedAt requires an ID field in the mutation")
+ }
+ oldValue, err := m.oldValue(ctx)
+ if err != nil {
+ return v, fmt.Errorf("querying old value for OldUsedAt: %w", err)
+ }
+ return oldValue.UsedAt, nil
+}
+
+// ResetUsedAt resets all changes to the "used_at" field.
+func (m *PromoCodeUsageMutation) ResetUsedAt() {
+ m.used_at = nil
+}
+
+// ClearPromoCode clears the "promo_code" edge to the PromoCode entity.
+func (m *PromoCodeUsageMutation) ClearPromoCode() {
+ m.clearedpromo_code = true
+ m.clearedFields[promocodeusage.FieldPromoCodeID] = struct{}{}
+}
+
+// PromoCodeCleared reports if the "promo_code" edge to the PromoCode entity was cleared.
+func (m *PromoCodeUsageMutation) PromoCodeCleared() bool {
+ return m.clearedpromo_code
+}
+
+// PromoCodeIDs returns the "promo_code" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// PromoCodeID instead. It exists only for internal usage by the builders.
+func (m *PromoCodeUsageMutation) PromoCodeIDs() (ids []int64) {
+ if id := m.promo_code; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetPromoCode resets all changes to the "promo_code" edge.
+func (m *PromoCodeUsageMutation) ResetPromoCode() {
+ m.promo_code = nil
+ m.clearedpromo_code = false
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (m *PromoCodeUsageMutation) ClearUser() {
+ m.cleareduser = true
+ m.clearedFields[promocodeusage.FieldUserID] = struct{}{}
+}
+
+// UserCleared reports if the "user" edge to the User entity was cleared.
+func (m *PromoCodeUsageMutation) UserCleared() bool {
+ return m.cleareduser
+}
+
+// UserIDs returns the "user" edge IDs in the mutation.
+// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
+// UserID instead. It exists only for internal usage by the builders.
+func (m *PromoCodeUsageMutation) UserIDs() (ids []int64) {
+ if id := m.user; id != nil {
+ ids = append(ids, *id)
+ }
+ return
+}
+
+// ResetUser resets all changes to the "user" edge.
+func (m *PromoCodeUsageMutation) ResetUser() {
+ m.user = nil
+ m.cleareduser = false
+}
+
+// Where appends a list predicates to the PromoCodeUsageMutation builder.
+func (m *PromoCodeUsageMutation) Where(ps ...predicate.PromoCodeUsage) {
+ m.predicates = append(m.predicates, ps...)
+}
+
+// WhereP appends storage-level predicates to the PromoCodeUsageMutation builder. Using this method,
+// users can use type-assertion to append predicates that do not depend on any generated package.
+func (m *PromoCodeUsageMutation) WhereP(ps ...func(*sql.Selector)) {
+ p := make([]predicate.PromoCodeUsage, len(ps))
+ for i := range ps {
+ p[i] = ps[i]
+ }
+ m.Where(p...)
+}
+
+// Op returns the operation name.
+func (m *PromoCodeUsageMutation) Op() Op {
+ return m.op
+}
+
+// SetOp allows setting the mutation operation.
+func (m *PromoCodeUsageMutation) SetOp(op Op) {
+ m.op = op
+}
+
+// Type returns the node type of this mutation (PromoCodeUsage).
+func (m *PromoCodeUsageMutation) Type() string {
+ return m.typ
+}
+
+// Fields returns all fields that were changed during this mutation. Note that in
+// order to get all numeric fields that were incremented/decremented, call
+// AddedFields().
+func (m *PromoCodeUsageMutation) Fields() []string {
+ fields := make([]string, 0, 4)
+ if m.promo_code != nil {
+ fields = append(fields, promocodeusage.FieldPromoCodeID)
+ }
+ if m.user != nil {
+ fields = append(fields, promocodeusage.FieldUserID)
+ }
+ if m.bonus_amount != nil {
+ fields = append(fields, promocodeusage.FieldBonusAmount)
+ }
+ if m.used_at != nil {
+ fields = append(fields, promocodeusage.FieldUsedAt)
+ }
+ return fields
+}
+
+// Field returns the value of a field with the given name. The second boolean
+// return value indicates that this field was not set, or was not defined in the
+// schema.
+func (m *PromoCodeUsageMutation) Field(name string) (ent.Value, bool) {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ return m.PromoCodeID()
+ case promocodeusage.FieldUserID:
+ return m.UserID()
+ case promocodeusage.FieldBonusAmount:
+ return m.BonusAmount()
+ case promocodeusage.FieldUsedAt:
+ return m.UsedAt()
+ }
+ return nil, false
+}
+
+// OldField returns the old value of the field from the database. An error is
+// returned if the mutation operation is not UpdateOne, or the query to the
+// database failed.
+func (m *PromoCodeUsageMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ return m.OldPromoCodeID(ctx)
+ case promocodeusage.FieldUserID:
+ return m.OldUserID(ctx)
+ case promocodeusage.FieldBonusAmount:
+ return m.OldBonusAmount(ctx)
+ case promocodeusage.FieldUsedAt:
+ return m.OldUsedAt(ctx)
+ }
+ return nil, fmt.Errorf("unknown PromoCodeUsage field %s", name)
+}
+
+// SetField sets the value of a field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeUsageMutation) SetField(name string, value ent.Value) error {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetPromoCodeID(v)
+ return nil
+ case promocodeusage.FieldUserID:
+ v, ok := value.(int64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUserID(v)
+ return nil
+ case promocodeusage.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetBonusAmount(v)
+ return nil
+ case promocodeusage.FieldUsedAt:
+ v, ok := value.(time.Time)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.SetUsedAt(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage field %s", name)
+}
+
+// AddedFields returns all numeric fields that were incremented/decremented during
+// this mutation.
+func (m *PromoCodeUsageMutation) AddedFields() []string {
+ var fields []string
+ if m.addbonus_amount != nil {
+ fields = append(fields, promocodeusage.FieldBonusAmount)
+ }
+ return fields
+}
+
+// AddedField returns the numeric value that was incremented/decremented on a field
+// with the given name. The second boolean return value indicates that this field
+// was not set, or was not defined in the schema.
+func (m *PromoCodeUsageMutation) AddedField(name string) (ent.Value, bool) {
+ switch name {
+ case promocodeusage.FieldBonusAmount:
+ return m.AddedBonusAmount()
+ }
+ return nil, false
+}
+
+// AddField adds the value to the field with the given name. It returns an error if
+// the field is not defined in the schema, or if the type mismatched the field
+// type.
+func (m *PromoCodeUsageMutation) AddField(name string, value ent.Value) error {
+ switch name {
+ case promocodeusage.FieldBonusAmount:
+ v, ok := value.(float64)
+ if !ok {
+ return fmt.Errorf("unexpected type %T for field %s", value, name)
+ }
+ m.AddBonusAmount(v)
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage numeric field %s", name)
+}
+
+// ClearedFields returns all nullable fields that were cleared during this
+// mutation.
+func (m *PromoCodeUsageMutation) ClearedFields() []string {
+ return nil
+}
+
+// FieldCleared returns a boolean indicating if a field with the given name was
+// cleared in this mutation.
+func (m *PromoCodeUsageMutation) FieldCleared(name string) bool {
+ _, ok := m.clearedFields[name]
+ return ok
+}
+
+// ClearField clears the value of the field with the given name. It returns an
+// error if the field is not defined in the schema.
+func (m *PromoCodeUsageMutation) ClearField(name string) error {
+ return fmt.Errorf("unknown PromoCodeUsage nullable field %s", name)
+}
+
+// ResetField resets all changes in the mutation for the field with the given name.
+// It returns an error if the field is not defined in the schema.
+func (m *PromoCodeUsageMutation) ResetField(name string) error {
+ switch name {
+ case promocodeusage.FieldPromoCodeID:
+ m.ResetPromoCodeID()
+ return nil
+ case promocodeusage.FieldUserID:
+ m.ResetUserID()
+ return nil
+ case promocodeusage.FieldBonusAmount:
+ m.ResetBonusAmount()
+ return nil
+ case promocodeusage.FieldUsedAt:
+ m.ResetUsedAt()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage field %s", name)
+}
+
+// AddedEdges returns all edge names that were set/added in this mutation.
+func (m *PromoCodeUsageMutation) AddedEdges() []string {
+ edges := make([]string, 0, 2)
+ if m.promo_code != nil {
+ edges = append(edges, promocodeusage.EdgePromoCode)
+ }
+ if m.user != nil {
+ edges = append(edges, promocodeusage.EdgeUser)
+ }
+ return edges
+}
+
+// AddedIDs returns all IDs (to other nodes) that were added for the given edge
+// name in this mutation.
+func (m *PromoCodeUsageMutation) AddedIDs(name string) []ent.Value {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ if id := m.promo_code; id != nil {
+ return []ent.Value{*id}
+ }
+ case promocodeusage.EdgeUser:
+ if id := m.user; id != nil {
+ return []ent.Value{*id}
+ }
+ }
+ return nil
+}
+
+// RemovedEdges returns all edge names that were removed in this mutation.
+func (m *PromoCodeUsageMutation) RemovedEdges() []string {
+ edges := make([]string, 0, 2)
+ return edges
+}
+
+// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
+// the given name in this mutation.
+func (m *PromoCodeUsageMutation) RemovedIDs(name string) []ent.Value {
+ return nil
+}
+
+// ClearedEdges returns all edge names that were cleared in this mutation.
+func (m *PromoCodeUsageMutation) ClearedEdges() []string {
+ edges := make([]string, 0, 2)
+ if m.clearedpromo_code {
+ edges = append(edges, promocodeusage.EdgePromoCode)
+ }
+ if m.cleareduser {
+ edges = append(edges, promocodeusage.EdgeUser)
+ }
+ return edges
+}
+
+// EdgeCleared returns a boolean which indicates if the edge with the given name
+// was cleared in this mutation.
+func (m *PromoCodeUsageMutation) EdgeCleared(name string) bool {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ return m.clearedpromo_code
+ case promocodeusage.EdgeUser:
+ return m.cleareduser
+ }
+ return false
+}
+
+// ClearEdge clears the value of the edge with the given name. It returns an error
+// if that edge is not defined in the schema.
+func (m *PromoCodeUsageMutation) ClearEdge(name string) error {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ m.ClearPromoCode()
+ return nil
+ case promocodeusage.EdgeUser:
+ m.ClearUser()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage unique edge %s", name)
+}
+
+// ResetEdge resets all changes to the edge with the given name in this mutation.
+// It returns an error if the edge is not defined in the schema.
+func (m *PromoCodeUsageMutation) ResetEdge(name string) error {
+ switch name {
+ case promocodeusage.EdgePromoCode:
+ m.ResetPromoCode()
+ return nil
+ case promocodeusage.EdgeUser:
+ m.ResetUser()
+ return nil
+ }
+ return fmt.Errorf("unknown PromoCodeUsage edge %s", name)
+}
+
// ProxyMutation represents an operation that mutates the Proxy nodes in the graph.
type ProxyMutation struct {
config
@@ -11353,6 +12975,9 @@ type UserMutation struct {
attribute_values map[int64]struct{}
removedattribute_values map[int64]struct{}
clearedattribute_values bool
+ promo_code_usages map[int64]struct{}
+ removedpromo_code_usages map[int64]struct{}
+ clearedpromo_code_usages bool
done bool
oldValue func(context.Context) (*User, error)
predicates []predicate.User
@@ -12283,6 +13908,60 @@ func (m *UserMutation) ResetAttributeValues() {
m.removedattribute_values = nil
}
+// AddPromoCodeUsageIDs adds the "promo_code_usages" edge to the PromoCodeUsage entity by ids.
+func (m *UserMutation) AddPromoCodeUsageIDs(ids ...int64) {
+ if m.promo_code_usages == nil {
+ m.promo_code_usages = make(map[int64]struct{})
+ }
+ for i := range ids {
+ m.promo_code_usages[ids[i]] = struct{}{}
+ }
+}
+
+// ClearPromoCodeUsages clears the "promo_code_usages" edge to the PromoCodeUsage entity.
+func (m *UserMutation) ClearPromoCodeUsages() {
+ m.clearedpromo_code_usages = true
+}
+
+// PromoCodeUsagesCleared reports if the "promo_code_usages" edge to the PromoCodeUsage entity was cleared.
+func (m *UserMutation) PromoCodeUsagesCleared() bool {
+ return m.clearedpromo_code_usages
+}
+
+// RemovePromoCodeUsageIDs removes the "promo_code_usages" edge to the PromoCodeUsage entity by IDs.
+func (m *UserMutation) RemovePromoCodeUsageIDs(ids ...int64) {
+ if m.removedpromo_code_usages == nil {
+ m.removedpromo_code_usages = make(map[int64]struct{})
+ }
+ for i := range ids {
+ delete(m.promo_code_usages, ids[i])
+ m.removedpromo_code_usages[ids[i]] = struct{}{}
+ }
+}
+
+// RemovedPromoCodeUsages returns the removed IDs of the "promo_code_usages" edge to the PromoCodeUsage entity.
+func (m *UserMutation) RemovedPromoCodeUsagesIDs() (ids []int64) {
+ for id := range m.removedpromo_code_usages {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// PromoCodeUsagesIDs returns the "promo_code_usages" edge IDs in the mutation.
+func (m *UserMutation) PromoCodeUsagesIDs() (ids []int64) {
+ for id := range m.promo_code_usages {
+ ids = append(ids, id)
+ }
+ return
+}
+
+// ResetPromoCodeUsages resets all changes to the "promo_code_usages" edge.
+func (m *UserMutation) ResetPromoCodeUsages() {
+ m.promo_code_usages = nil
+ m.clearedpromo_code_usages = false
+ m.removedpromo_code_usages = nil
+}
+
// Where appends a list predicates to the UserMutation builder.
func (m *UserMutation) Where(ps ...predicate.User) {
m.predicates = append(m.predicates, ps...)
@@ -12622,7 +14301,7 @@ func (m *UserMutation) ResetField(name string) error {
// AddedEdges returns all edge names that were set/added in this mutation.
func (m *UserMutation) AddedEdges() []string {
- edges := make([]string, 0, 7)
+ edges := make([]string, 0, 8)
if m.api_keys != nil {
edges = append(edges, user.EdgeAPIKeys)
}
@@ -12644,6 +14323,9 @@ func (m *UserMutation) AddedEdges() []string {
if m.attribute_values != nil {
edges = append(edges, user.EdgeAttributeValues)
}
+ if m.promo_code_usages != nil {
+ edges = append(edges, user.EdgePromoCodeUsages)
+ }
return edges
}
@@ -12693,13 +14375,19 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
+ case user.EdgePromoCodeUsages:
+ ids := make([]ent.Value, 0, len(m.promo_code_usages))
+ for id := range m.promo_code_usages {
+ ids = append(ids, id)
+ }
+ return ids
}
return nil
}
// RemovedEdges returns all edge names that were removed in this mutation.
func (m *UserMutation) RemovedEdges() []string {
- edges := make([]string, 0, 7)
+ edges := make([]string, 0, 8)
if m.removedapi_keys != nil {
edges = append(edges, user.EdgeAPIKeys)
}
@@ -12721,6 +14409,9 @@ func (m *UserMutation) RemovedEdges() []string {
if m.removedattribute_values != nil {
edges = append(edges, user.EdgeAttributeValues)
}
+ if m.removedpromo_code_usages != nil {
+ edges = append(edges, user.EdgePromoCodeUsages)
+ }
return edges
}
@@ -12770,13 +14461,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value {
ids = append(ids, id)
}
return ids
+ case user.EdgePromoCodeUsages:
+ ids := make([]ent.Value, 0, len(m.removedpromo_code_usages))
+ for id := range m.removedpromo_code_usages {
+ ids = append(ids, id)
+ }
+ return ids
}
return nil
}
// ClearedEdges returns all edge names that were cleared in this mutation.
func (m *UserMutation) ClearedEdges() []string {
- edges := make([]string, 0, 7)
+ edges := make([]string, 0, 8)
if m.clearedapi_keys {
edges = append(edges, user.EdgeAPIKeys)
}
@@ -12798,6 +14495,9 @@ func (m *UserMutation) ClearedEdges() []string {
if m.clearedattribute_values {
edges = append(edges, user.EdgeAttributeValues)
}
+ if m.clearedpromo_code_usages {
+ edges = append(edges, user.EdgePromoCodeUsages)
+ }
return edges
}
@@ -12819,6 +14519,8 @@ func (m *UserMutation) EdgeCleared(name string) bool {
return m.clearedusage_logs
case user.EdgeAttributeValues:
return m.clearedattribute_values
+ case user.EdgePromoCodeUsages:
+ return m.clearedpromo_code_usages
}
return false
}
@@ -12856,6 +14558,9 @@ func (m *UserMutation) ResetEdge(name string) error {
case user.EdgeAttributeValues:
m.ResetAttributeValues()
return nil
+ case user.EdgePromoCodeUsages:
+ m.ResetPromoCodeUsages()
+ return nil
}
return fmt.Errorf("unknown User edge %s", name)
}
diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go
index 87c56902..7a443c5d 100644
--- a/backend/ent/predicate/predicate.go
+++ b/backend/ent/predicate/predicate.go
@@ -18,6 +18,12 @@ type AccountGroup func(*sql.Selector)
// Group is the predicate function for group builders.
type Group func(*sql.Selector)
+// PromoCode is the predicate function for promocode builders.
+type PromoCode func(*sql.Selector)
+
+// PromoCodeUsage is the predicate function for promocodeusage builders.
+type PromoCodeUsage func(*sql.Selector)
+
// Proxy is the predicate function for proxy builders.
type Proxy func(*sql.Selector)
diff --git a/backend/ent/promocode.go b/backend/ent/promocode.go
new file mode 100644
index 00000000..1123bbd6
--- /dev/null
+++ b/backend/ent/promocode.go
@@ -0,0 +1,228 @@
+// 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/promocode"
+)
+
+// PromoCode is the model entity for the PromoCode schema.
+type PromoCode struct {
+ config `json:"-"`
+ // ID of the ent.
+ ID int64 `json:"id,omitempty"`
+ // 优惠码
+ Code string `json:"code,omitempty"`
+ // 赠送余额金额
+ BonusAmount float64 `json:"bonus_amount,omitempty"`
+ // 最大使用次数,0表示无限制
+ MaxUses int `json:"max_uses,omitempty"`
+ // 已使用次数
+ UsedCount int `json:"used_count,omitempty"`
+ // 状态: active, disabled
+ Status string `json:"status,omitempty"`
+ // 过期时间,null表示永不过期
+ ExpiresAt *time.Time `json:"expires_at,omitempty"`
+ // 备注
+ Notes *string `json:"notes,omitempty"`
+ // CreatedAt holds the value of the "created_at" field.
+ CreatedAt time.Time `json:"created_at,omitempty"`
+ // UpdatedAt holds the value of the "updated_at" field.
+ UpdatedAt time.Time `json:"updated_at,omitempty"`
+ // Edges holds the relations/edges for other nodes in the graph.
+ // The values are being populated by the PromoCodeQuery when eager-loading is set.
+ Edges PromoCodeEdges `json:"edges"`
+ selectValues sql.SelectValues
+}
+
+// PromoCodeEdges holds the relations/edges for other nodes in the graph.
+type PromoCodeEdges struct {
+ // UsageRecords holds the value of the usage_records edge.
+ UsageRecords []*PromoCodeUsage `json:"usage_records,omitempty"`
+ // loadedTypes holds the information for reporting if a
+ // type was loaded (or requested) in eager-loading or not.
+ loadedTypes [1]bool
+}
+
+// UsageRecordsOrErr returns the UsageRecords value or an error if the edge
+// was not loaded in eager-loading.
+func (e PromoCodeEdges) UsageRecordsOrErr() ([]*PromoCodeUsage, error) {
+ if e.loadedTypes[0] {
+ return e.UsageRecords, nil
+ }
+ return nil, &NotLoadedError{edge: "usage_records"}
+}
+
+// scanValues returns the types for scanning values from sql.Rows.
+func (*PromoCode) scanValues(columns []string) ([]any, error) {
+ values := make([]any, len(columns))
+ for i := range columns {
+ switch columns[i] {
+ case promocode.FieldBonusAmount:
+ values[i] = new(sql.NullFloat64)
+ case promocode.FieldID, promocode.FieldMaxUses, promocode.FieldUsedCount:
+ values[i] = new(sql.NullInt64)
+ case promocode.FieldCode, promocode.FieldStatus, promocode.FieldNotes:
+ values[i] = new(sql.NullString)
+ case promocode.FieldExpiresAt, promocode.FieldCreatedAt, promocode.FieldUpdatedAt:
+ 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 PromoCode fields.
+func (_m *PromoCode) 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 promocode.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 promocode.FieldCode:
+ if value, ok := values[i].(*sql.NullString); !ok {
+ return fmt.Errorf("unexpected type %T for field code", values[i])
+ } else if value.Valid {
+ _m.Code = value.String
+ }
+ case promocode.FieldBonusAmount:
+ if value, ok := values[i].(*sql.NullFloat64); !ok {
+ return fmt.Errorf("unexpected type %T for field bonus_amount", values[i])
+ } else if value.Valid {
+ _m.BonusAmount = value.Float64
+ }
+ case promocode.FieldMaxUses:
+ if value, ok := values[i].(*sql.NullInt64); !ok {
+ return fmt.Errorf("unexpected type %T for field max_uses", values[i])
+ } else if value.Valid {
+ _m.MaxUses = int(value.Int64)
+ }
+ case promocode.FieldUsedCount:
+ if value, ok := values[i].(*sql.NullInt64); !ok {
+ return fmt.Errorf("unexpected type %T for field used_count", values[i])
+ } else if value.Valid {
+ _m.UsedCount = int(value.Int64)
+ }
+ case promocode.FieldStatus:
+ if value, ok := values[i].(*sql.NullString); !ok {
+ return fmt.Errorf("unexpected type %T for field status", values[i])
+ } else if value.Valid {
+ _m.Status = value.String
+ }
+ case promocode.FieldExpiresAt:
+ if value, ok := values[i].(*sql.NullTime); !ok {
+ return fmt.Errorf("unexpected type %T for field expires_at", values[i])
+ } else if value.Valid {
+ _m.ExpiresAt = new(time.Time)
+ *_m.ExpiresAt = value.Time
+ }
+ case promocode.FieldNotes:
+ if value, ok := values[i].(*sql.NullString); !ok {
+ return fmt.Errorf("unexpected type %T for field notes", values[i])
+ } else if value.Valid {
+ _m.Notes = new(string)
+ *_m.Notes = value.String
+ }
+ case promocode.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
+ }
+ case promocode.FieldUpdatedAt:
+ if value, ok := values[i].(*sql.NullTime); !ok {
+ return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+ } else if value.Valid {
+ _m.UpdatedAt = 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 PromoCode.
+// This includes values selected through modifiers, order, etc.
+func (_m *PromoCode) Value(name string) (ent.Value, error) {
+ return _m.selectValues.Get(name)
+}
+
+// QueryUsageRecords queries the "usage_records" edge of the PromoCode entity.
+func (_m *PromoCode) QueryUsageRecords() *PromoCodeUsageQuery {
+ return NewPromoCodeClient(_m.config).QueryUsageRecords(_m)
+}
+
+// Update returns a builder for updating this PromoCode.
+// Note that you need to call PromoCode.Unwrap() before calling this method if this PromoCode
+// was returned from a transaction, and the transaction was committed or rolled back.
+func (_m *PromoCode) Update() *PromoCodeUpdateOne {
+ return NewPromoCodeClient(_m.config).UpdateOne(_m)
+}
+
+// Unwrap unwraps the PromoCode 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 *PromoCode) Unwrap() *PromoCode {
+ _tx, ok := _m.config.driver.(*txDriver)
+ if !ok {
+ panic("ent: PromoCode is not a transactional entity")
+ }
+ _m.config.driver = _tx.drv
+ return _m
+}
+
+// String implements the fmt.Stringer.
+func (_m *PromoCode) String() string {
+ var builder strings.Builder
+ builder.WriteString("PromoCode(")
+ builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
+ builder.WriteString("code=")
+ builder.WriteString(_m.Code)
+ builder.WriteString(", ")
+ builder.WriteString("bonus_amount=")
+ builder.WriteString(fmt.Sprintf("%v", _m.BonusAmount))
+ builder.WriteString(", ")
+ builder.WriteString("max_uses=")
+ builder.WriteString(fmt.Sprintf("%v", _m.MaxUses))
+ builder.WriteString(", ")
+ builder.WriteString("used_count=")
+ builder.WriteString(fmt.Sprintf("%v", _m.UsedCount))
+ builder.WriteString(", ")
+ builder.WriteString("status=")
+ builder.WriteString(_m.Status)
+ builder.WriteString(", ")
+ if v := _m.ExpiresAt; v != nil {
+ builder.WriteString("expires_at=")
+ builder.WriteString(v.Format(time.ANSIC))
+ }
+ builder.WriteString(", ")
+ if v := _m.Notes; v != nil {
+ builder.WriteString("notes=")
+ builder.WriteString(*v)
+ }
+ builder.WriteString(", ")
+ builder.WriteString("created_at=")
+ builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
+ builder.WriteString(", ")
+ builder.WriteString("updated_at=")
+ builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
+ builder.WriteByte(')')
+ return builder.String()
+}
+
+// PromoCodes is a parsable slice of PromoCode.
+type PromoCodes []*PromoCode
diff --git a/backend/ent/promocode/promocode.go b/backend/ent/promocode/promocode.go
new file mode 100644
index 00000000..ba91658f
--- /dev/null
+++ b/backend/ent/promocode/promocode.go
@@ -0,0 +1,165 @@
+// Code generated by ent, DO NOT EDIT.
+
+package promocode
+
+import (
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+)
+
+const (
+ // Label holds the string label denoting the promocode type in the database.
+ Label = "promo_code"
+ // FieldID holds the string denoting the id field in the database.
+ FieldID = "id"
+ // FieldCode holds the string denoting the code field in the database.
+ FieldCode = "code"
+ // FieldBonusAmount holds the string denoting the bonus_amount field in the database.
+ FieldBonusAmount = "bonus_amount"
+ // FieldMaxUses holds the string denoting the max_uses field in the database.
+ FieldMaxUses = "max_uses"
+ // FieldUsedCount holds the string denoting the used_count field in the database.
+ FieldUsedCount = "used_count"
+ // FieldStatus holds the string denoting the status field in the database.
+ FieldStatus = "status"
+ // FieldExpiresAt holds the string denoting the expires_at field in the database.
+ FieldExpiresAt = "expires_at"
+ // FieldNotes holds the string denoting the notes field in the database.
+ FieldNotes = "notes"
+ // FieldCreatedAt holds the string denoting the created_at field in the database.
+ FieldCreatedAt = "created_at"
+ // FieldUpdatedAt holds the string denoting the updated_at field in the database.
+ FieldUpdatedAt = "updated_at"
+ // EdgeUsageRecords holds the string denoting the usage_records edge name in mutations.
+ EdgeUsageRecords = "usage_records"
+ // Table holds the table name of the promocode in the database.
+ Table = "promo_codes"
+ // UsageRecordsTable is the table that holds the usage_records relation/edge.
+ UsageRecordsTable = "promo_code_usages"
+ // UsageRecordsInverseTable is the table name for the PromoCodeUsage entity.
+ // It exists in this package in order to avoid circular dependency with the "promocodeusage" package.
+ UsageRecordsInverseTable = "promo_code_usages"
+ // UsageRecordsColumn is the table column denoting the usage_records relation/edge.
+ UsageRecordsColumn = "promo_code_id"
+)
+
+// Columns holds all SQL columns for promocode fields.
+var Columns = []string{
+ FieldID,
+ FieldCode,
+ FieldBonusAmount,
+ FieldMaxUses,
+ FieldUsedCount,
+ FieldStatus,
+ FieldExpiresAt,
+ FieldNotes,
+ FieldCreatedAt,
+ FieldUpdatedAt,
+}
+
+// ValidColumn reports if the column name is valid (part of the table columns).
+func ValidColumn(column string) bool {
+ for i := range Columns {
+ if column == Columns[i] {
+ return true
+ }
+ }
+ return false
+}
+
+var (
+ // CodeValidator is a validator for the "code" field. It is called by the builders before save.
+ CodeValidator func(string) error
+ // DefaultBonusAmount holds the default value on creation for the "bonus_amount" field.
+ DefaultBonusAmount float64
+ // DefaultMaxUses holds the default value on creation for the "max_uses" field.
+ DefaultMaxUses int
+ // DefaultUsedCount holds the default value on creation for the "used_count" field.
+ DefaultUsedCount int
+ // DefaultStatus holds the default value on creation for the "status" field.
+ DefaultStatus string
+ // StatusValidator is a validator for the "status" field. It is called by the builders before save.
+ StatusValidator func(string) error
+ // DefaultCreatedAt holds the default value on creation for the "created_at" field.
+ DefaultCreatedAt func() time.Time
+ // DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
+ DefaultUpdatedAt func() time.Time
+ // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
+ UpdateDefaultUpdatedAt func() time.Time
+)
+
+// OrderOption defines the ordering options for the PromoCode queries.
+type OrderOption func(*sql.Selector)
+
+// ByID orders the results by the id field.
+func ByID(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldID, opts...).ToFunc()
+}
+
+// ByCode orders the results by the code field.
+func ByCode(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldCode, opts...).ToFunc()
+}
+
+// ByBonusAmount orders the results by the bonus_amount field.
+func ByBonusAmount(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldBonusAmount, opts...).ToFunc()
+}
+
+// ByMaxUses orders the results by the max_uses field.
+func ByMaxUses(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldMaxUses, opts...).ToFunc()
+}
+
+// ByUsedCount orders the results by the used_count field.
+func ByUsedCount(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldUsedCount, opts...).ToFunc()
+}
+
+// ByStatus orders the results by the status field.
+func ByStatus(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldStatus, opts...).ToFunc()
+}
+
+// ByExpiresAt orders the results by the expires_at field.
+func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
+}
+
+// ByNotes orders the results by the notes field.
+func ByNotes(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldNotes, opts...).ToFunc()
+}
+
+// ByCreatedAt orders the results by the created_at field.
+func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
+}
+
+// ByUpdatedAt orders the results by the updated_at field.
+func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
+}
+
+// ByUsageRecordsCount orders the results by usage_records count.
+func ByUsageRecordsCount(opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborsCount(s, newUsageRecordsStep(), opts...)
+ }
+}
+
+// ByUsageRecords orders the results by usage_records terms.
+func ByUsageRecords(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newUsageRecordsStep(), append([]sql.OrderTerm{term}, terms...)...)
+ }
+}
+func newUsageRecordsStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(UsageRecordsInverseTable, FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, UsageRecordsTable, UsageRecordsColumn),
+ )
+}
diff --git a/backend/ent/promocode/where.go b/backend/ent/promocode/where.go
new file mode 100644
index 00000000..84b6460a
--- /dev/null
+++ b/backend/ent/promocode/where.go
@@ -0,0 +1,594 @@
+// Code generated by ent, DO NOT EDIT.
+
+package promocode
+
+import (
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+ "github.com/Wei-Shaw/sub2api/ent/predicate"
+)
+
+// ID filters vertices based on their ID field.
+func ID(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldID, id))
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldID, id))
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldID, id))
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldID, ids...))
+}
+
+// IDNotIn applies the NotIn predicate on the ID field.
+func IDNotIn(ids ...int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldID, ids...))
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldID, id))
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldID, id))
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldID, id))
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id int64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldID, id))
+}
+
+// Code applies equality check predicate on the "code" field. It's identical to CodeEQ.
+func Code(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldCode, v))
+}
+
+// BonusAmount applies equality check predicate on the "bonus_amount" field. It's identical to BonusAmountEQ.
+func BonusAmount(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldBonusAmount, v))
+}
+
+// MaxUses applies equality check predicate on the "max_uses" field. It's identical to MaxUsesEQ.
+func MaxUses(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldMaxUses, v))
+}
+
+// UsedCount applies equality check predicate on the "used_count" field. It's identical to UsedCountEQ.
+func UsedCount(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldUsedCount, v))
+}
+
+// Status applies equality check predicate on the "status" field. It's identical to StatusEQ.
+func Status(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldStatus, v))
+}
+
+// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
+func ExpiresAt(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldExpiresAt, v))
+}
+
+// Notes applies equality check predicate on the "notes" field. It's identical to NotesEQ.
+func Notes(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldNotes, v))
+}
+
+// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
+func CreatedAt(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
+func UpdatedAt(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// CodeEQ applies the EQ predicate on the "code" field.
+func CodeEQ(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldCode, v))
+}
+
+// CodeNEQ applies the NEQ predicate on the "code" field.
+func CodeNEQ(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldCode, v))
+}
+
+// CodeIn applies the In predicate on the "code" field.
+func CodeIn(vs ...string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldCode, vs...))
+}
+
+// CodeNotIn applies the NotIn predicate on the "code" field.
+func CodeNotIn(vs ...string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldCode, vs...))
+}
+
+// CodeGT applies the GT predicate on the "code" field.
+func CodeGT(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldCode, v))
+}
+
+// CodeGTE applies the GTE predicate on the "code" field.
+func CodeGTE(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldCode, v))
+}
+
+// CodeLT applies the LT predicate on the "code" field.
+func CodeLT(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldCode, v))
+}
+
+// CodeLTE applies the LTE predicate on the "code" field.
+func CodeLTE(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldCode, v))
+}
+
+// CodeContains applies the Contains predicate on the "code" field.
+func CodeContains(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldContains(FieldCode, v))
+}
+
+// CodeHasPrefix applies the HasPrefix predicate on the "code" field.
+func CodeHasPrefix(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldHasPrefix(FieldCode, v))
+}
+
+// CodeHasSuffix applies the HasSuffix predicate on the "code" field.
+func CodeHasSuffix(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldHasSuffix(FieldCode, v))
+}
+
+// CodeEqualFold applies the EqualFold predicate on the "code" field.
+func CodeEqualFold(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEqualFold(FieldCode, v))
+}
+
+// CodeContainsFold applies the ContainsFold predicate on the "code" field.
+func CodeContainsFold(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldContainsFold(FieldCode, v))
+}
+
+// BonusAmountEQ applies the EQ predicate on the "bonus_amount" field.
+func BonusAmountEQ(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldBonusAmount, v))
+}
+
+// BonusAmountNEQ applies the NEQ predicate on the "bonus_amount" field.
+func BonusAmountNEQ(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldBonusAmount, v))
+}
+
+// BonusAmountIn applies the In predicate on the "bonus_amount" field.
+func BonusAmountIn(vs ...float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldBonusAmount, vs...))
+}
+
+// BonusAmountNotIn applies the NotIn predicate on the "bonus_amount" field.
+func BonusAmountNotIn(vs ...float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldBonusAmount, vs...))
+}
+
+// BonusAmountGT applies the GT predicate on the "bonus_amount" field.
+func BonusAmountGT(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldBonusAmount, v))
+}
+
+// BonusAmountGTE applies the GTE predicate on the "bonus_amount" field.
+func BonusAmountGTE(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldBonusAmount, v))
+}
+
+// BonusAmountLT applies the LT predicate on the "bonus_amount" field.
+func BonusAmountLT(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldBonusAmount, v))
+}
+
+// BonusAmountLTE applies the LTE predicate on the "bonus_amount" field.
+func BonusAmountLTE(v float64) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldBonusAmount, v))
+}
+
+// MaxUsesEQ applies the EQ predicate on the "max_uses" field.
+func MaxUsesEQ(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldMaxUses, v))
+}
+
+// MaxUsesNEQ applies the NEQ predicate on the "max_uses" field.
+func MaxUsesNEQ(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldMaxUses, v))
+}
+
+// MaxUsesIn applies the In predicate on the "max_uses" field.
+func MaxUsesIn(vs ...int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldMaxUses, vs...))
+}
+
+// MaxUsesNotIn applies the NotIn predicate on the "max_uses" field.
+func MaxUsesNotIn(vs ...int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldMaxUses, vs...))
+}
+
+// MaxUsesGT applies the GT predicate on the "max_uses" field.
+func MaxUsesGT(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldMaxUses, v))
+}
+
+// MaxUsesGTE applies the GTE predicate on the "max_uses" field.
+func MaxUsesGTE(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldMaxUses, v))
+}
+
+// MaxUsesLT applies the LT predicate on the "max_uses" field.
+func MaxUsesLT(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldMaxUses, v))
+}
+
+// MaxUsesLTE applies the LTE predicate on the "max_uses" field.
+func MaxUsesLTE(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldMaxUses, v))
+}
+
+// UsedCountEQ applies the EQ predicate on the "used_count" field.
+func UsedCountEQ(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldUsedCount, v))
+}
+
+// UsedCountNEQ applies the NEQ predicate on the "used_count" field.
+func UsedCountNEQ(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldUsedCount, v))
+}
+
+// UsedCountIn applies the In predicate on the "used_count" field.
+func UsedCountIn(vs ...int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldUsedCount, vs...))
+}
+
+// UsedCountNotIn applies the NotIn predicate on the "used_count" field.
+func UsedCountNotIn(vs ...int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldUsedCount, vs...))
+}
+
+// UsedCountGT applies the GT predicate on the "used_count" field.
+func UsedCountGT(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldUsedCount, v))
+}
+
+// UsedCountGTE applies the GTE predicate on the "used_count" field.
+func UsedCountGTE(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldUsedCount, v))
+}
+
+// UsedCountLT applies the LT predicate on the "used_count" field.
+func UsedCountLT(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldUsedCount, v))
+}
+
+// UsedCountLTE applies the LTE predicate on the "used_count" field.
+func UsedCountLTE(v int) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldUsedCount, v))
+}
+
+// StatusEQ applies the EQ predicate on the "status" field.
+func StatusEQ(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldStatus, v))
+}
+
+// StatusNEQ applies the NEQ predicate on the "status" field.
+func StatusNEQ(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldStatus, v))
+}
+
+// StatusIn applies the In predicate on the "status" field.
+func StatusIn(vs ...string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldStatus, vs...))
+}
+
+// StatusNotIn applies the NotIn predicate on the "status" field.
+func StatusNotIn(vs ...string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldStatus, vs...))
+}
+
+// StatusGT applies the GT predicate on the "status" field.
+func StatusGT(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldStatus, v))
+}
+
+// StatusGTE applies the GTE predicate on the "status" field.
+func StatusGTE(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldStatus, v))
+}
+
+// StatusLT applies the LT predicate on the "status" field.
+func StatusLT(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldStatus, v))
+}
+
+// StatusLTE applies the LTE predicate on the "status" field.
+func StatusLTE(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldStatus, v))
+}
+
+// StatusContains applies the Contains predicate on the "status" field.
+func StatusContains(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldContains(FieldStatus, v))
+}
+
+// StatusHasPrefix applies the HasPrefix predicate on the "status" field.
+func StatusHasPrefix(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldHasPrefix(FieldStatus, v))
+}
+
+// StatusHasSuffix applies the HasSuffix predicate on the "status" field.
+func StatusHasSuffix(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldHasSuffix(FieldStatus, v))
+}
+
+// StatusEqualFold applies the EqualFold predicate on the "status" field.
+func StatusEqualFold(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEqualFold(FieldStatus, v))
+}
+
+// StatusContainsFold applies the ContainsFold predicate on the "status" field.
+func StatusContainsFold(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldContainsFold(FieldStatus, v))
+}
+
+// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
+func ExpiresAtEQ(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldExpiresAt, v))
+}
+
+// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
+func ExpiresAtNEQ(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldExpiresAt, v))
+}
+
+// ExpiresAtIn applies the In predicate on the "expires_at" field.
+func ExpiresAtIn(vs ...time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldExpiresAt, vs...))
+}
+
+// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
+func ExpiresAtNotIn(vs ...time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldExpiresAt, vs...))
+}
+
+// ExpiresAtGT applies the GT predicate on the "expires_at" field.
+func ExpiresAtGT(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldExpiresAt, v))
+}
+
+// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
+func ExpiresAtGTE(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldExpiresAt, v))
+}
+
+// ExpiresAtLT applies the LT predicate on the "expires_at" field.
+func ExpiresAtLT(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldExpiresAt, v))
+}
+
+// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
+func ExpiresAtLTE(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldExpiresAt, v))
+}
+
+// ExpiresAtIsNil applies the IsNil predicate on the "expires_at" field.
+func ExpiresAtIsNil() predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIsNull(FieldExpiresAt))
+}
+
+// ExpiresAtNotNil applies the NotNil predicate on the "expires_at" field.
+func ExpiresAtNotNil() predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotNull(FieldExpiresAt))
+}
+
+// NotesEQ applies the EQ predicate on the "notes" field.
+func NotesEQ(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldNotes, v))
+}
+
+// NotesNEQ applies the NEQ predicate on the "notes" field.
+func NotesNEQ(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldNotes, v))
+}
+
+// NotesIn applies the In predicate on the "notes" field.
+func NotesIn(vs ...string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldNotes, vs...))
+}
+
+// NotesNotIn applies the NotIn predicate on the "notes" field.
+func NotesNotIn(vs ...string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldNotes, vs...))
+}
+
+// NotesGT applies the GT predicate on the "notes" field.
+func NotesGT(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldNotes, v))
+}
+
+// NotesGTE applies the GTE predicate on the "notes" field.
+func NotesGTE(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldNotes, v))
+}
+
+// NotesLT applies the LT predicate on the "notes" field.
+func NotesLT(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldNotes, v))
+}
+
+// NotesLTE applies the LTE predicate on the "notes" field.
+func NotesLTE(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldNotes, v))
+}
+
+// NotesContains applies the Contains predicate on the "notes" field.
+func NotesContains(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldContains(FieldNotes, v))
+}
+
+// NotesHasPrefix applies the HasPrefix predicate on the "notes" field.
+func NotesHasPrefix(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldHasPrefix(FieldNotes, v))
+}
+
+// NotesHasSuffix applies the HasSuffix predicate on the "notes" field.
+func NotesHasSuffix(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldHasSuffix(FieldNotes, v))
+}
+
+// NotesIsNil applies the IsNil predicate on the "notes" field.
+func NotesIsNil() predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIsNull(FieldNotes))
+}
+
+// NotesNotNil applies the NotNil predicate on the "notes" field.
+func NotesNotNil() predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotNull(FieldNotes))
+}
+
+// NotesEqualFold applies the EqualFold predicate on the "notes" field.
+func NotesEqualFold(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEqualFold(FieldNotes, v))
+}
+
+// NotesContainsFold applies the ContainsFold predicate on the "notes" field.
+func NotesContainsFold(v string) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldContainsFold(FieldNotes, v))
+}
+
+// CreatedAtEQ applies the EQ predicate on the "created_at" field.
+func CreatedAtEQ(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
+func CreatedAtNEQ(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldCreatedAt, v))
+}
+
+// CreatedAtIn applies the In predicate on the "created_at" field.
+func CreatedAtIn(vs ...time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
+func CreatedAtNotIn(vs ...time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldCreatedAt, vs...))
+}
+
+// CreatedAtGT applies the GT predicate on the "created_at" field.
+func CreatedAtGT(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldCreatedAt, v))
+}
+
+// CreatedAtGTE applies the GTE predicate on the "created_at" field.
+func CreatedAtGTE(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldCreatedAt, v))
+}
+
+// CreatedAtLT applies the LT predicate on the "created_at" field.
+func CreatedAtLT(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldCreatedAt, v))
+}
+
+// CreatedAtLTE applies the LTE predicate on the "created_at" field.
+func CreatedAtLTE(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldCreatedAt, v))
+}
+
+// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
+func UpdatedAtEQ(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
+func UpdatedAtNEQ(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNEQ(FieldUpdatedAt, v))
+}
+
+// UpdatedAtIn applies the In predicate on the "updated_at" field.
+func UpdatedAtIn(vs ...time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
+func UpdatedAtNotIn(vs ...time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldNotIn(FieldUpdatedAt, vs...))
+}
+
+// UpdatedAtGT applies the GT predicate on the "updated_at" field.
+func UpdatedAtGT(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
+func UpdatedAtGTE(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldGTE(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLT applies the LT predicate on the "updated_at" field.
+func UpdatedAtLT(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLT(FieldUpdatedAt, v))
+}
+
+// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
+func UpdatedAtLTE(v time.Time) predicate.PromoCode {
+ return predicate.PromoCode(sql.FieldLTE(FieldUpdatedAt, v))
+}
+
+// HasUsageRecords applies the HasEdge predicate on the "usage_records" edge.
+func HasUsageRecords() predicate.PromoCode {
+ return predicate.PromoCode(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, UsageRecordsTable, UsageRecordsColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasUsageRecordsWith applies the HasEdge predicate on the "usage_records" edge with a given conditions (other predicates).
+func HasUsageRecordsWith(preds ...predicate.PromoCodeUsage) predicate.PromoCode {
+ return predicate.PromoCode(func(s *sql.Selector) {
+ step := newUsageRecordsStep()
+ 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.PromoCode) predicate.PromoCode {
+ return predicate.PromoCode(sql.AndPredicates(predicates...))
+}
+
+// Or groups predicates with the OR operator between them.
+func Or(predicates ...predicate.PromoCode) predicate.PromoCode {
+ return predicate.PromoCode(sql.OrPredicates(predicates...))
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.PromoCode) predicate.PromoCode {
+ return predicate.PromoCode(sql.NotPredicates(p))
+}
diff --git a/backend/ent/promocode_create.go b/backend/ent/promocode_create.go
new file mode 100644
index 00000000..4fd2c39c
--- /dev/null
+++ b/backend/ent/promocode_create.go
@@ -0,0 +1,1081 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+ "entgo.io/ent/schema/field"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+)
+
+// PromoCodeCreate is the builder for creating a PromoCode entity.
+type PromoCodeCreate struct {
+ config
+ mutation *PromoCodeMutation
+ hooks []Hook
+ conflict []sql.ConflictOption
+}
+
+// SetCode sets the "code" field.
+func (_c *PromoCodeCreate) SetCode(v string) *PromoCodeCreate {
+ _c.mutation.SetCode(v)
+ return _c
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (_c *PromoCodeCreate) SetBonusAmount(v float64) *PromoCodeCreate {
+ _c.mutation.SetBonusAmount(v)
+ return _c
+}
+
+// SetNillableBonusAmount sets the "bonus_amount" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableBonusAmount(v *float64) *PromoCodeCreate {
+ if v != nil {
+ _c.SetBonusAmount(*v)
+ }
+ return _c
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (_c *PromoCodeCreate) SetMaxUses(v int) *PromoCodeCreate {
+ _c.mutation.SetMaxUses(v)
+ return _c
+}
+
+// SetNillableMaxUses sets the "max_uses" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableMaxUses(v *int) *PromoCodeCreate {
+ if v != nil {
+ _c.SetMaxUses(*v)
+ }
+ return _c
+}
+
+// SetUsedCount sets the "used_count" field.
+func (_c *PromoCodeCreate) SetUsedCount(v int) *PromoCodeCreate {
+ _c.mutation.SetUsedCount(v)
+ return _c
+}
+
+// SetNillableUsedCount sets the "used_count" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableUsedCount(v *int) *PromoCodeCreate {
+ if v != nil {
+ _c.SetUsedCount(*v)
+ }
+ return _c
+}
+
+// SetStatus sets the "status" field.
+func (_c *PromoCodeCreate) SetStatus(v string) *PromoCodeCreate {
+ _c.mutation.SetStatus(v)
+ return _c
+}
+
+// SetNillableStatus sets the "status" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableStatus(v *string) *PromoCodeCreate {
+ if v != nil {
+ _c.SetStatus(*v)
+ }
+ return _c
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (_c *PromoCodeCreate) SetExpiresAt(v time.Time) *PromoCodeCreate {
+ _c.mutation.SetExpiresAt(v)
+ return _c
+}
+
+// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableExpiresAt(v *time.Time) *PromoCodeCreate {
+ if v != nil {
+ _c.SetExpiresAt(*v)
+ }
+ return _c
+}
+
+// SetNotes sets the "notes" field.
+func (_c *PromoCodeCreate) SetNotes(v string) *PromoCodeCreate {
+ _c.mutation.SetNotes(v)
+ return _c
+}
+
+// SetNillableNotes sets the "notes" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableNotes(v *string) *PromoCodeCreate {
+ if v != nil {
+ _c.SetNotes(*v)
+ }
+ return _c
+}
+
+// SetCreatedAt sets the "created_at" field.
+func (_c *PromoCodeCreate) SetCreatedAt(v time.Time) *PromoCodeCreate {
+ _c.mutation.SetCreatedAt(v)
+ return _c
+}
+
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableCreatedAt(v *time.Time) *PromoCodeCreate {
+ if v != nil {
+ _c.SetCreatedAt(*v)
+ }
+ return _c
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_c *PromoCodeCreate) SetUpdatedAt(v time.Time) *PromoCodeCreate {
+ _c.mutation.SetUpdatedAt(v)
+ return _c
+}
+
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
+func (_c *PromoCodeCreate) SetNillableUpdatedAt(v *time.Time) *PromoCodeCreate {
+ if v != nil {
+ _c.SetUpdatedAt(*v)
+ }
+ return _c
+}
+
+// AddUsageRecordIDs adds the "usage_records" edge to the PromoCodeUsage entity by IDs.
+func (_c *PromoCodeCreate) AddUsageRecordIDs(ids ...int64) *PromoCodeCreate {
+ _c.mutation.AddUsageRecordIDs(ids...)
+ return _c
+}
+
+// AddUsageRecords adds the "usage_records" edges to the PromoCodeUsage entity.
+func (_c *PromoCodeCreate) AddUsageRecords(v ...*PromoCodeUsage) *PromoCodeCreate {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _c.AddUsageRecordIDs(ids...)
+}
+
+// Mutation returns the PromoCodeMutation object of the builder.
+func (_c *PromoCodeCreate) Mutation() *PromoCodeMutation {
+ return _c.mutation
+}
+
+// Save creates the PromoCode in the database.
+func (_c *PromoCodeCreate) Save(ctx context.Context) (*PromoCode, error) {
+ _c.defaults()
+ return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (_c *PromoCodeCreate) SaveX(ctx context.Context) *PromoCode {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PromoCodeCreate) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PromoCodeCreate) ExecX(ctx context.Context) {
+ if err := _c.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (_c *PromoCodeCreate) defaults() {
+ if _, ok := _c.mutation.BonusAmount(); !ok {
+ v := promocode.DefaultBonusAmount
+ _c.mutation.SetBonusAmount(v)
+ }
+ if _, ok := _c.mutation.MaxUses(); !ok {
+ v := promocode.DefaultMaxUses
+ _c.mutation.SetMaxUses(v)
+ }
+ if _, ok := _c.mutation.UsedCount(); !ok {
+ v := promocode.DefaultUsedCount
+ _c.mutation.SetUsedCount(v)
+ }
+ if _, ok := _c.mutation.Status(); !ok {
+ v := promocode.DefaultStatus
+ _c.mutation.SetStatus(v)
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ v := promocode.DefaultCreatedAt()
+ _c.mutation.SetCreatedAt(v)
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ v := promocode.DefaultUpdatedAt()
+ _c.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_c *PromoCodeCreate) check() error {
+ if _, ok := _c.mutation.Code(); !ok {
+ return &ValidationError{Name: "code", err: errors.New(`ent: missing required field "PromoCode.code"`)}
+ }
+ if v, ok := _c.mutation.Code(); ok {
+ if err := promocode.CodeValidator(v); err != nil {
+ return &ValidationError{Name: "code", err: fmt.Errorf(`ent: validator failed for field "PromoCode.code": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.BonusAmount(); !ok {
+ return &ValidationError{Name: "bonus_amount", err: errors.New(`ent: missing required field "PromoCode.bonus_amount"`)}
+ }
+ if _, ok := _c.mutation.MaxUses(); !ok {
+ return &ValidationError{Name: "max_uses", err: errors.New(`ent: missing required field "PromoCode.max_uses"`)}
+ }
+ if _, ok := _c.mutation.UsedCount(); !ok {
+ return &ValidationError{Name: "used_count", err: errors.New(`ent: missing required field "PromoCode.used_count"`)}
+ }
+ if _, ok := _c.mutation.Status(); !ok {
+ return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "PromoCode.status"`)}
+ }
+ if v, ok := _c.mutation.Status(); ok {
+ if err := promocode.StatusValidator(v); err != nil {
+ return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PromoCode.status": %w`, err)}
+ }
+ }
+ if _, ok := _c.mutation.CreatedAt(); !ok {
+ return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PromoCode.created_at"`)}
+ }
+ if _, ok := _c.mutation.UpdatedAt(); !ok {
+ return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "PromoCode.updated_at"`)}
+ }
+ return nil
+}
+
+func (_c *PromoCodeCreate) sqlSave(ctx context.Context) (*PromoCode, error) {
+ if err := _c.check(); err != nil {
+ return nil, err
+ }
+ _node, _spec := _c.createSpec()
+ if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
+ if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return nil, err
+ }
+ id := _spec.ID.Value.(int64)
+ _node.ID = int64(id)
+ _c.mutation.id = &_node.ID
+ _c.mutation.done = true
+ return _node, nil
+}
+
+func (_c *PromoCodeCreate) createSpec() (*PromoCode, *sqlgraph.CreateSpec) {
+ var (
+ _node = &PromoCode{config: _c.config}
+ _spec = sqlgraph.NewCreateSpec(promocode.Table, sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64))
+ )
+ _spec.OnConflict = _c.conflict
+ if value, ok := _c.mutation.Code(); ok {
+ _spec.SetField(promocode.FieldCode, field.TypeString, value)
+ _node.Code = value
+ }
+ if value, ok := _c.mutation.BonusAmount(); ok {
+ _spec.SetField(promocode.FieldBonusAmount, field.TypeFloat64, value)
+ _node.BonusAmount = value
+ }
+ if value, ok := _c.mutation.MaxUses(); ok {
+ _spec.SetField(promocode.FieldMaxUses, field.TypeInt, value)
+ _node.MaxUses = value
+ }
+ if value, ok := _c.mutation.UsedCount(); ok {
+ _spec.SetField(promocode.FieldUsedCount, field.TypeInt, value)
+ _node.UsedCount = value
+ }
+ if value, ok := _c.mutation.Status(); ok {
+ _spec.SetField(promocode.FieldStatus, field.TypeString, value)
+ _node.Status = value
+ }
+ if value, ok := _c.mutation.ExpiresAt(); ok {
+ _spec.SetField(promocode.FieldExpiresAt, field.TypeTime, value)
+ _node.ExpiresAt = &value
+ }
+ if value, ok := _c.mutation.Notes(); ok {
+ _spec.SetField(promocode.FieldNotes, field.TypeString, value)
+ _node.Notes = &value
+ }
+ if value, ok := _c.mutation.CreatedAt(); ok {
+ _spec.SetField(promocode.FieldCreatedAt, field.TypeTime, value)
+ _node.CreatedAt = value
+ }
+ if value, ok := _c.mutation.UpdatedAt(); ok {
+ _spec.SetField(promocode.FieldUpdatedAt, field.TypeTime, value)
+ _node.UpdatedAt = value
+ }
+ if nodes := _c.mutation.UsageRecordsIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges = append(_spec.Edges, edge)
+ }
+ return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.PromoCode.Create().
+// SetCode(v).
+// OnConflict(
+// // Update the row with the new values
+// // the was proposed for insertion.
+// sql.ResolveWithNewValues(),
+// ).
+// // Override some of the fields with custom
+// // update values.
+// Update(func(u *ent.PromoCodeUpsert) {
+// SetCode(v+v).
+// }).
+// Exec(ctx)
+func (_c *PromoCodeCreate) OnConflict(opts ...sql.ConflictOption) *PromoCodeUpsertOne {
+ _c.conflict = opts
+ return &PromoCodeUpsertOne{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PromoCode.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PromoCodeCreate) OnConflictColumns(columns ...string) *PromoCodeUpsertOne {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PromoCodeUpsertOne{
+ create: _c,
+ }
+}
+
+type (
+ // PromoCodeUpsertOne is the builder for "upsert"-ing
+ // one PromoCode node.
+ PromoCodeUpsertOne struct {
+ create *PromoCodeCreate
+ }
+
+ // PromoCodeUpsert is the "OnConflict" setter.
+ PromoCodeUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetCode sets the "code" field.
+func (u *PromoCodeUpsert) SetCode(v string) *PromoCodeUpsert {
+ u.Set(promocode.FieldCode, v)
+ return u
+}
+
+// UpdateCode sets the "code" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateCode() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldCode)
+ return u
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (u *PromoCodeUpsert) SetBonusAmount(v float64) *PromoCodeUpsert {
+ u.Set(promocode.FieldBonusAmount, v)
+ return u
+}
+
+// UpdateBonusAmount sets the "bonus_amount" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateBonusAmount() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldBonusAmount)
+ return u
+}
+
+// AddBonusAmount adds v to the "bonus_amount" field.
+func (u *PromoCodeUpsert) AddBonusAmount(v float64) *PromoCodeUpsert {
+ u.Add(promocode.FieldBonusAmount, v)
+ return u
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (u *PromoCodeUpsert) SetMaxUses(v int) *PromoCodeUpsert {
+ u.Set(promocode.FieldMaxUses, v)
+ return u
+}
+
+// UpdateMaxUses sets the "max_uses" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateMaxUses() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldMaxUses)
+ return u
+}
+
+// AddMaxUses adds v to the "max_uses" field.
+func (u *PromoCodeUpsert) AddMaxUses(v int) *PromoCodeUpsert {
+ u.Add(promocode.FieldMaxUses, v)
+ return u
+}
+
+// SetUsedCount sets the "used_count" field.
+func (u *PromoCodeUpsert) SetUsedCount(v int) *PromoCodeUpsert {
+ u.Set(promocode.FieldUsedCount, v)
+ return u
+}
+
+// UpdateUsedCount sets the "used_count" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateUsedCount() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldUsedCount)
+ return u
+}
+
+// AddUsedCount adds v to the "used_count" field.
+func (u *PromoCodeUpsert) AddUsedCount(v int) *PromoCodeUpsert {
+ u.Add(promocode.FieldUsedCount, v)
+ return u
+}
+
+// SetStatus sets the "status" field.
+func (u *PromoCodeUpsert) SetStatus(v string) *PromoCodeUpsert {
+ u.Set(promocode.FieldStatus, v)
+ return u
+}
+
+// UpdateStatus sets the "status" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateStatus() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldStatus)
+ return u
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (u *PromoCodeUpsert) SetExpiresAt(v time.Time) *PromoCodeUpsert {
+ u.Set(promocode.FieldExpiresAt, v)
+ return u
+}
+
+// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateExpiresAt() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldExpiresAt)
+ return u
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (u *PromoCodeUpsert) ClearExpiresAt() *PromoCodeUpsert {
+ u.SetNull(promocode.FieldExpiresAt)
+ return u
+}
+
+// SetNotes sets the "notes" field.
+func (u *PromoCodeUpsert) SetNotes(v string) *PromoCodeUpsert {
+ u.Set(promocode.FieldNotes, v)
+ return u
+}
+
+// UpdateNotes sets the "notes" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateNotes() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldNotes)
+ return u
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (u *PromoCodeUpsert) ClearNotes() *PromoCodeUpsert {
+ u.SetNull(promocode.FieldNotes)
+ return u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PromoCodeUpsert) SetUpdatedAt(v time.Time) *PromoCodeUpsert {
+ u.Set(promocode.FieldUpdatedAt, v)
+ return u
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PromoCodeUpsert) UpdateUpdatedAt() *PromoCodeUpsert {
+ u.SetExcluded(promocode.FieldUpdatedAt)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create.
+// Using this option is equivalent to using:
+//
+// client.PromoCode.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PromoCodeUpsertOne) UpdateNewValues() *PromoCodeUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
+ if _, exists := u.create.mutation.CreatedAt(); exists {
+ s.SetIgnore(promocode.FieldCreatedAt)
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PromoCode.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PromoCodeUpsertOne) Ignore() *PromoCodeUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+ return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *PromoCodeUpsertOne) DoNothing() *PromoCodeUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PromoCodeCreate.OnConflict
+// documentation for more info.
+func (u *PromoCodeUpsertOne) Update(set func(*PromoCodeUpsert)) *PromoCodeUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PromoCodeUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetCode sets the "code" field.
+func (u *PromoCodeUpsertOne) SetCode(v string) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetCode(v)
+ })
+}
+
+// UpdateCode sets the "code" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateCode() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateCode()
+ })
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (u *PromoCodeUpsertOne) SetBonusAmount(v float64) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetBonusAmount(v)
+ })
+}
+
+// AddBonusAmount adds v to the "bonus_amount" field.
+func (u *PromoCodeUpsertOne) AddBonusAmount(v float64) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.AddBonusAmount(v)
+ })
+}
+
+// UpdateBonusAmount sets the "bonus_amount" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateBonusAmount() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateBonusAmount()
+ })
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (u *PromoCodeUpsertOne) SetMaxUses(v int) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetMaxUses(v)
+ })
+}
+
+// AddMaxUses adds v to the "max_uses" field.
+func (u *PromoCodeUpsertOne) AddMaxUses(v int) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.AddMaxUses(v)
+ })
+}
+
+// UpdateMaxUses sets the "max_uses" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateMaxUses() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateMaxUses()
+ })
+}
+
+// SetUsedCount sets the "used_count" field.
+func (u *PromoCodeUpsertOne) SetUsedCount(v int) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetUsedCount(v)
+ })
+}
+
+// AddUsedCount adds v to the "used_count" field.
+func (u *PromoCodeUpsertOne) AddUsedCount(v int) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.AddUsedCount(v)
+ })
+}
+
+// UpdateUsedCount sets the "used_count" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateUsedCount() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateUsedCount()
+ })
+}
+
+// SetStatus sets the "status" field.
+func (u *PromoCodeUpsertOne) SetStatus(v string) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetStatus(v)
+ })
+}
+
+// UpdateStatus sets the "status" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateStatus() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateStatus()
+ })
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (u *PromoCodeUpsertOne) SetExpiresAt(v time.Time) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetExpiresAt(v)
+ })
+}
+
+// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateExpiresAt() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateExpiresAt()
+ })
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (u *PromoCodeUpsertOne) ClearExpiresAt() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.ClearExpiresAt()
+ })
+}
+
+// SetNotes sets the "notes" field.
+func (u *PromoCodeUpsertOne) SetNotes(v string) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetNotes(v)
+ })
+}
+
+// UpdateNotes sets the "notes" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateNotes() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateNotes()
+ })
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (u *PromoCodeUpsertOne) ClearNotes() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.ClearNotes()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PromoCodeUpsertOne) SetUpdatedAt(v time.Time) *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PromoCodeUpsertOne) UpdateUpdatedAt() *PromoCodeUpsertOne {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PromoCodeUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PromoCodeCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PromoCodeUpsertOne) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// Exec executes the UPSERT query and returns the inserted/updated ID.
+func (u *PromoCodeUpsertOne) ID(ctx context.Context) (id int64, err error) {
+ node, err := u.create.Save(ctx)
+ if err != nil {
+ return id, err
+ }
+ return node.ID, nil
+}
+
+// IDX is like ID, but panics if an error occurs.
+func (u *PromoCodeUpsertOne) IDX(ctx context.Context) int64 {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// PromoCodeCreateBulk is the builder for creating many PromoCode entities in bulk.
+type PromoCodeCreateBulk struct {
+ config
+ err error
+ builders []*PromoCodeCreate
+ conflict []sql.ConflictOption
+}
+
+// Save creates the PromoCode entities in the database.
+func (_c *PromoCodeCreateBulk) Save(ctx context.Context) ([]*PromoCode, error) {
+ if _c.err != nil {
+ return nil, _c.err
+ }
+ specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
+ nodes := make([]*PromoCode, len(_c.builders))
+ mutators := make([]Mutator, len(_c.builders))
+ for i := range _c.builders {
+ func(i int, root context.Context) {
+ builder := _c.builders[i]
+ builder.defaults()
+ var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
+ mutation, ok := m.(*PromoCodeMutation)
+ if !ok {
+ return nil, fmt.Errorf("unexpected mutation type %T", m)
+ }
+ if err := builder.check(); err != nil {
+ return nil, err
+ }
+ builder.mutation = mutation
+ var err error
+ nodes[i], specs[i] = builder.createSpec()
+ if i < len(mutators)-1 {
+ _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
+ } else {
+ spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
+ spec.OnConflict = _c.conflict
+ // Invoke the actual operation on the latest mutation in the chain.
+ if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
+ if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+ mutation.id = &nodes[i].ID
+ if specs[i].ID.Value != nil {
+ id := specs[i].ID.Value.(int64)
+ nodes[i].ID = int64(id)
+ }
+ mutation.done = true
+ return nodes[i], nil
+ })
+ for i := len(builder.hooks) - 1; i >= 0; i-- {
+ mut = builder.hooks[i](mut)
+ }
+ mutators[i] = mut
+ }(i, ctx)
+ }
+ if len(mutators) > 0 {
+ if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
+ return nil, err
+ }
+ }
+ return nodes, nil
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_c *PromoCodeCreateBulk) SaveX(ctx context.Context) []*PromoCode {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PromoCodeCreateBulk) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PromoCodeCreateBulk) ExecX(ctx context.Context) {
+ if err := _c.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.PromoCode.CreateBulk(builders...).
+// OnConflict(
+// // Update the row with the new values
+// // the was proposed for insertion.
+// sql.ResolveWithNewValues(),
+// ).
+// // Override some of the fields with custom
+// // update values.
+// Update(func(u *ent.PromoCodeUpsert) {
+// SetCode(v+v).
+// }).
+// Exec(ctx)
+func (_c *PromoCodeCreateBulk) OnConflict(opts ...sql.ConflictOption) *PromoCodeUpsertBulk {
+ _c.conflict = opts
+ return &PromoCodeUpsertBulk{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PromoCode.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PromoCodeCreateBulk) OnConflictColumns(columns ...string) *PromoCodeUpsertBulk {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PromoCodeUpsertBulk{
+ create: _c,
+ }
+}
+
+// PromoCodeUpsertBulk is the builder for "upsert"-ing
+// a bulk of PromoCode nodes.
+type PromoCodeUpsertBulk struct {
+ create *PromoCodeCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.PromoCode.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PromoCodeUpsertBulk) UpdateNewValues() *PromoCodeUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
+ for _, b := range u.create.builders {
+ if _, exists := b.mutation.CreatedAt(); exists {
+ s.SetIgnore(promocode.FieldCreatedAt)
+ }
+ }
+ }))
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PromoCode.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PromoCodeUpsertBulk) Ignore() *PromoCodeUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+ return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *PromoCodeUpsertBulk) DoNothing() *PromoCodeUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PromoCodeCreateBulk.OnConflict
+// documentation for more info.
+func (u *PromoCodeUpsertBulk) Update(set func(*PromoCodeUpsert)) *PromoCodeUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PromoCodeUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetCode sets the "code" field.
+func (u *PromoCodeUpsertBulk) SetCode(v string) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetCode(v)
+ })
+}
+
+// UpdateCode sets the "code" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateCode() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateCode()
+ })
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (u *PromoCodeUpsertBulk) SetBonusAmount(v float64) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetBonusAmount(v)
+ })
+}
+
+// AddBonusAmount adds v to the "bonus_amount" field.
+func (u *PromoCodeUpsertBulk) AddBonusAmount(v float64) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.AddBonusAmount(v)
+ })
+}
+
+// UpdateBonusAmount sets the "bonus_amount" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateBonusAmount() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateBonusAmount()
+ })
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (u *PromoCodeUpsertBulk) SetMaxUses(v int) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetMaxUses(v)
+ })
+}
+
+// AddMaxUses adds v to the "max_uses" field.
+func (u *PromoCodeUpsertBulk) AddMaxUses(v int) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.AddMaxUses(v)
+ })
+}
+
+// UpdateMaxUses sets the "max_uses" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateMaxUses() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateMaxUses()
+ })
+}
+
+// SetUsedCount sets the "used_count" field.
+func (u *PromoCodeUpsertBulk) SetUsedCount(v int) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetUsedCount(v)
+ })
+}
+
+// AddUsedCount adds v to the "used_count" field.
+func (u *PromoCodeUpsertBulk) AddUsedCount(v int) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.AddUsedCount(v)
+ })
+}
+
+// UpdateUsedCount sets the "used_count" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateUsedCount() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateUsedCount()
+ })
+}
+
+// SetStatus sets the "status" field.
+func (u *PromoCodeUpsertBulk) SetStatus(v string) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetStatus(v)
+ })
+}
+
+// UpdateStatus sets the "status" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateStatus() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateStatus()
+ })
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (u *PromoCodeUpsertBulk) SetExpiresAt(v time.Time) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetExpiresAt(v)
+ })
+}
+
+// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateExpiresAt() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateExpiresAt()
+ })
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (u *PromoCodeUpsertBulk) ClearExpiresAt() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.ClearExpiresAt()
+ })
+}
+
+// SetNotes sets the "notes" field.
+func (u *PromoCodeUpsertBulk) SetNotes(v string) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetNotes(v)
+ })
+}
+
+// UpdateNotes sets the "notes" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateNotes() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateNotes()
+ })
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (u *PromoCodeUpsertBulk) ClearNotes() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.ClearNotes()
+ })
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (u *PromoCodeUpsertBulk) SetUpdatedAt(v time.Time) *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.SetUpdatedAt(v)
+ })
+}
+
+// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
+func (u *PromoCodeUpsertBulk) UpdateUpdatedAt() *PromoCodeUpsertBulk {
+ return u.Update(func(s *PromoCodeUpsert) {
+ s.UpdateUpdatedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PromoCodeUpsertBulk) Exec(ctx context.Context) error {
+ if u.create.err != nil {
+ return u.create.err
+ }
+ for i, b := range u.create.builders {
+ if len(b.conflict) != 0 {
+ return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the PromoCodeCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PromoCodeCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PromoCodeUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/promocode_delete.go b/backend/ent/promocode_delete.go
new file mode 100644
index 00000000..7e4fa3a6
--- /dev/null
+++ b/backend/ent/promocode_delete.go
@@ -0,0 +1,88 @@
+// 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/promocode"
+)
+
+// PromoCodeDelete is the builder for deleting a PromoCode entity.
+type PromoCodeDelete struct {
+ config
+ hooks []Hook
+ mutation *PromoCodeMutation
+}
+
+// Where appends a list predicates to the PromoCodeDelete builder.
+func (_d *PromoCodeDelete) Where(ps ...predicate.PromoCode) *PromoCodeDelete {
+ _d.mutation.Where(ps...)
+ return _d
+}
+
+// Exec executes the deletion query and returns how many vertices were deleted.
+func (_d *PromoCodeDelete) 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 *PromoCodeDelete) ExecX(ctx context.Context) int {
+ n, err := _d.Exec(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func (_d *PromoCodeDelete) sqlExec(ctx context.Context) (int, error) {
+ _spec := sqlgraph.NewDeleteSpec(promocode.Table, sqlgraph.NewFieldSpec(promocode.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
+}
+
+// PromoCodeDeleteOne is the builder for deleting a single PromoCode entity.
+type PromoCodeDeleteOne struct {
+ _d *PromoCodeDelete
+}
+
+// Where appends a list predicates to the PromoCodeDelete builder.
+func (_d *PromoCodeDeleteOne) Where(ps ...predicate.PromoCode) *PromoCodeDeleteOne {
+ _d._d.mutation.Where(ps...)
+ return _d
+}
+
+// Exec executes the deletion query.
+func (_d *PromoCodeDeleteOne) Exec(ctx context.Context) error {
+ n, err := _d._d.Exec(ctx)
+ switch {
+ case err != nil:
+ return err
+ case n == 0:
+ return &NotFoundError{promocode.Label}
+ default:
+ return nil
+ }
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_d *PromoCodeDeleteOne) ExecX(ctx context.Context) {
+ if err := _d.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/promocode_query.go b/backend/ent/promocode_query.go
new file mode 100644
index 00000000..2156b0f0
--- /dev/null
+++ b/backend/ent/promocode_query.go
@@ -0,0 +1,643 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+ "context"
+ "database/sql/driver"
+ "fmt"
+ "math"
+
+ "entgo.io/ent"
+ "entgo.io/ent/dialect"
+ "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/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+)
+
+// PromoCodeQuery is the builder for querying PromoCode entities.
+type PromoCodeQuery struct {
+ config
+ ctx *QueryContext
+ order []promocode.OrderOption
+ inters []Interceptor
+ predicates []predicate.PromoCode
+ withUsageRecords *PromoCodeUsageQuery
+ modifiers []func(*sql.Selector)
+ // intermediate query (i.e. traversal path).
+ sql *sql.Selector
+ path func(context.Context) (*sql.Selector, error)
+}
+
+// Where adds a new predicate for the PromoCodeQuery builder.
+func (_q *PromoCodeQuery) Where(ps ...predicate.PromoCode) *PromoCodeQuery {
+ _q.predicates = append(_q.predicates, ps...)
+ return _q
+}
+
+// Limit the number of records to be returned by this query.
+func (_q *PromoCodeQuery) Limit(limit int) *PromoCodeQuery {
+ _q.ctx.Limit = &limit
+ return _q
+}
+
+// Offset to start from.
+func (_q *PromoCodeQuery) Offset(offset int) *PromoCodeQuery {
+ _q.ctx.Offset = &offset
+ return _q
+}
+
+// Unique configures the query builder to filter duplicate records on query.
+// By default, unique is set to true, and can be disabled using this method.
+func (_q *PromoCodeQuery) Unique(unique bool) *PromoCodeQuery {
+ _q.ctx.Unique = &unique
+ return _q
+}
+
+// Order specifies how the records should be ordered.
+func (_q *PromoCodeQuery) Order(o ...promocode.OrderOption) *PromoCodeQuery {
+ _q.order = append(_q.order, o...)
+ return _q
+}
+
+// QueryUsageRecords chains the current query on the "usage_records" edge.
+func (_q *PromoCodeQuery) QueryUsageRecords() *PromoCodeUsageQuery {
+ query := (&PromoCodeUsageClient{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(promocode.Table, promocode.FieldID, selector),
+ sqlgraph.To(promocodeusage.Table, promocodeusage.FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, promocode.UsageRecordsTable, promocode.UsageRecordsColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
+// First returns the first PromoCode entity from the query.
+// Returns a *NotFoundError when no PromoCode was found.
+func (_q *PromoCodeQuery) First(ctx context.Context) (*PromoCode, error) {
+ nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
+ if err != nil {
+ return nil, err
+ }
+ if len(nodes) == 0 {
+ return nil, &NotFoundError{promocode.Label}
+ }
+ return nodes[0], nil
+}
+
+// FirstX is like First, but panics if an error occurs.
+func (_q *PromoCodeQuery) FirstX(ctx context.Context) *PromoCode {
+ node, err := _q.First(ctx)
+ if err != nil && !IsNotFound(err) {
+ panic(err)
+ }
+ return node
+}
+
+// FirstID returns the first PromoCode ID from the query.
+// Returns a *NotFoundError when no PromoCode ID was found.
+func (_q *PromoCodeQuery) FirstID(ctx context.Context) (id int64, err error) {
+ var ids []int64
+ if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
+ return
+ }
+ if len(ids) == 0 {
+ err = &NotFoundError{promocode.Label}
+ return
+ }
+ return ids[0], nil
+}
+
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (_q *PromoCodeQuery) FirstIDX(ctx context.Context) int64 {
+ id, err := _q.FirstID(ctx)
+ if err != nil && !IsNotFound(err) {
+ panic(err)
+ }
+ return id
+}
+
+// Only returns a single PromoCode entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when more than one PromoCode entity is found.
+// Returns a *NotFoundError when no PromoCode entities are found.
+func (_q *PromoCodeQuery) Only(ctx context.Context) (*PromoCode, error) {
+ nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
+ if err != nil {
+ return nil, err
+ }
+ switch len(nodes) {
+ case 1:
+ return nodes[0], nil
+ case 0:
+ return nil, &NotFoundError{promocode.Label}
+ default:
+ return nil, &NotSingularError{promocode.Label}
+ }
+}
+
+// OnlyX is like Only, but panics if an error occurs.
+func (_q *PromoCodeQuery) OnlyX(ctx context.Context) *PromoCode {
+ node, err := _q.Only(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// OnlyID is like Only, but returns the only PromoCode ID in the query.
+// Returns a *NotSingularError when more than one PromoCode ID is found.
+// Returns a *NotFoundError when no entities are found.
+func (_q *PromoCodeQuery) OnlyID(ctx context.Context) (id int64, err error) {
+ var ids []int64
+ if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
+ return
+ }
+ switch len(ids) {
+ case 1:
+ id = ids[0]
+ case 0:
+ err = &NotFoundError{promocode.Label}
+ default:
+ err = &NotSingularError{promocode.Label}
+ }
+ return
+}
+
+// OnlyIDX is like OnlyID, but panics if an error occurs.
+func (_q *PromoCodeQuery) OnlyIDX(ctx context.Context) int64 {
+ id, err := _q.OnlyID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// All executes the query and returns a list of PromoCodes.
+func (_q *PromoCodeQuery) All(ctx context.Context) ([]*PromoCode, error) {
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
+ if err := _q.prepareQuery(ctx); err != nil {
+ return nil, err
+ }
+ qr := querierAll[[]*PromoCode, *PromoCodeQuery]()
+ return withInterceptors[[]*PromoCode](ctx, _q, qr, _q.inters)
+}
+
+// AllX is like All, but panics if an error occurs.
+func (_q *PromoCodeQuery) AllX(ctx context.Context) []*PromoCode {
+ nodes, err := _q.All(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return nodes
+}
+
+// IDs executes the query and returns a list of PromoCode IDs.
+func (_q *PromoCodeQuery) IDs(ctx context.Context) (ids []int64, err error) {
+ if _q.ctx.Unique == nil && _q.path != nil {
+ _q.Unique(true)
+ }
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
+ if err = _q.Select(promocode.FieldID).Scan(ctx, &ids); err != nil {
+ return nil, err
+ }
+ return ids, nil
+}
+
+// IDsX is like IDs, but panics if an error occurs.
+func (_q *PromoCodeQuery) IDsX(ctx context.Context) []int64 {
+ ids, err := _q.IDs(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return ids
+}
+
+// Count returns the count of the given query.
+func (_q *PromoCodeQuery) Count(ctx context.Context) (int, error) {
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
+ if err := _q.prepareQuery(ctx); err != nil {
+ return 0, err
+ }
+ return withInterceptors[int](ctx, _q, querierCount[*PromoCodeQuery](), _q.inters)
+}
+
+// CountX is like Count, but panics if an error occurs.
+func (_q *PromoCodeQuery) CountX(ctx context.Context) int {
+ count, err := _q.Count(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return count
+}
+
+// Exist returns true if the query has elements in the graph.
+func (_q *PromoCodeQuery) Exist(ctx context.Context) (bool, error) {
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
+ switch _, err := _q.FirstID(ctx); {
+ case IsNotFound(err):
+ return false, nil
+ case err != nil:
+ return false, fmt.Errorf("ent: check existence: %w", err)
+ default:
+ return true, nil
+ }
+}
+
+// ExistX is like Exist, but panics if an error occurs.
+func (_q *PromoCodeQuery) ExistX(ctx context.Context) bool {
+ exist, err := _q.Exist(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return exist
+}
+
+// Clone returns a duplicate of the PromoCodeQuery builder, including all associated steps. It can be
+// used to prepare common query builders and use them differently after the clone is made.
+func (_q *PromoCodeQuery) Clone() *PromoCodeQuery {
+ if _q == nil {
+ return nil
+ }
+ return &PromoCodeQuery{
+ config: _q.config,
+ ctx: _q.ctx.Clone(),
+ order: append([]promocode.OrderOption{}, _q.order...),
+ inters: append([]Interceptor{}, _q.inters...),
+ predicates: append([]predicate.PromoCode{}, _q.predicates...),
+ withUsageRecords: _q.withUsageRecords.Clone(),
+ // clone intermediate query.
+ sql: _q.sql.Clone(),
+ path: _q.path,
+ }
+}
+
+// WithUsageRecords tells the query-builder to eager-load the nodes that are connected to
+// the "usage_records" edge. The optional arguments are used to configure the query builder of the edge.
+func (_q *PromoCodeQuery) WithUsageRecords(opts ...func(*PromoCodeUsageQuery)) *PromoCodeQuery {
+ query := (&PromoCodeUsageClient{config: _q.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ _q.withUsageRecords = 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.
+//
+// Example:
+//
+// var v []struct {
+// Code string `json:"code,omitempty"`
+// Count int `json:"count,omitempty"`
+// }
+//
+// client.PromoCode.Query().
+// GroupBy(promocode.FieldCode).
+// Aggregate(ent.Count()).
+// Scan(ctx, &v)
+func (_q *PromoCodeQuery) GroupBy(field string, fields ...string) *PromoCodeGroupBy {
+ _q.ctx.Fields = append([]string{field}, fields...)
+ grbuild := &PromoCodeGroupBy{build: _q}
+ grbuild.flds = &_q.ctx.Fields
+ grbuild.label = promocode.Label
+ grbuild.scan = grbuild.Scan
+ return grbuild
+}
+
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
+//
+// Example:
+//
+// var v []struct {
+// Code string `json:"code,omitempty"`
+// }
+//
+// client.PromoCode.Query().
+// Select(promocode.FieldCode).
+// Scan(ctx, &v)
+func (_q *PromoCodeQuery) Select(fields ...string) *PromoCodeSelect {
+ _q.ctx.Fields = append(_q.ctx.Fields, fields...)
+ sbuild := &PromoCodeSelect{PromoCodeQuery: _q}
+ sbuild.label = promocode.Label
+ sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
+ return sbuild
+}
+
+// Aggregate returns a PromoCodeSelect configured with the given aggregations.
+func (_q *PromoCodeQuery) Aggregate(fns ...AggregateFunc) *PromoCodeSelect {
+ return _q.Select().Aggregate(fns...)
+}
+
+func (_q *PromoCodeQuery) prepareQuery(ctx context.Context) error {
+ for _, inter := range _q.inters {
+ if inter == nil {
+ return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
+ }
+ if trv, ok := inter.(Traverser); ok {
+ if err := trv.Traverse(ctx, _q); err != nil {
+ return err
+ }
+ }
+ }
+ for _, f := range _q.ctx.Fields {
+ if !promocode.ValidColumn(f) {
+ return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ }
+ if _q.path != nil {
+ prev, err := _q.path(ctx)
+ if err != nil {
+ return err
+ }
+ _q.sql = prev
+ }
+ return nil
+}
+
+func (_q *PromoCodeQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PromoCode, error) {
+ var (
+ nodes = []*PromoCode{}
+ _spec = _q.querySpec()
+ loadedTypes = [1]bool{
+ _q.withUsageRecords != nil,
+ }
+ )
+ _spec.ScanValues = func(columns []string) ([]any, error) {
+ return (*PromoCode).scanValues(nil, columns)
+ }
+ _spec.Assign = func(columns []string, values []any) error {
+ node := &PromoCode{config: _q.config}
+ nodes = append(nodes, node)
+ node.Edges.loadedTypes = loadedTypes
+ return node.assignValues(columns, values)
+ }
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
+ for i := range hooks {
+ hooks[i](ctx, _spec)
+ }
+ if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
+ return nil, err
+ }
+ if len(nodes) == 0 {
+ return nodes, nil
+ }
+ if query := _q.withUsageRecords; query != nil {
+ if err := _q.loadUsageRecords(ctx, query, nodes,
+ func(n *PromoCode) { n.Edges.UsageRecords = []*PromoCodeUsage{} },
+ func(n *PromoCode, e *PromoCodeUsage) { n.Edges.UsageRecords = append(n.Edges.UsageRecords, e) }); err != nil {
+ return nil, err
+ }
+ }
+ return nodes, nil
+}
+
+func (_q *PromoCodeQuery) loadUsageRecords(ctx context.Context, query *PromoCodeUsageQuery, nodes []*PromoCode, init func(*PromoCode), assign func(*PromoCode, *PromoCodeUsage)) error {
+ fks := make([]driver.Value, 0, len(nodes))
+ nodeids := make(map[int64]*PromoCode)
+ 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(promocodeusage.FieldPromoCodeID)
+ }
+ query.Where(predicate.PromoCodeUsage(func(s *sql.Selector) {
+ s.Where(sql.InValues(s.C(promocode.UsageRecordsColumn), fks...))
+ }))
+ neighbors, err := query.All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, n := range neighbors {
+ fk := n.PromoCodeID
+ node, ok := nodeids[fk]
+ if !ok {
+ return fmt.Errorf(`unexpected referenced foreign-key "promo_code_id" returned %v for node %v`, fk, n.ID)
+ }
+ assign(node, n)
+ }
+ return nil
+}
+
+func (_q *PromoCodeQuery) sqlCount(ctx context.Context) (int, error) {
+ _spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
+ _spec.Node.Columns = _q.ctx.Fields
+ if len(_q.ctx.Fields) > 0 {
+ _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
+ }
+ return sqlgraph.CountNodes(ctx, _q.driver, _spec)
+}
+
+func (_q *PromoCodeQuery) querySpec() *sqlgraph.QuerySpec {
+ _spec := sqlgraph.NewQuerySpec(promocode.Table, promocode.Columns, sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64))
+ _spec.From = _q.sql
+ if unique := _q.ctx.Unique; unique != nil {
+ _spec.Unique = *unique
+ } else if _q.path != nil {
+ _spec.Unique = true
+ }
+ if fields := _q.ctx.Fields; len(fields) > 0 {
+ _spec.Node.Columns = make([]string, 0, len(fields))
+ _spec.Node.Columns = append(_spec.Node.Columns, promocode.FieldID)
+ for i := range fields {
+ if fields[i] != promocode.FieldID {
+ _spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+ }
+ }
+ }
+ if ps := _q.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if limit := _q.ctx.Limit; limit != nil {
+ _spec.Limit = *limit
+ }
+ if offset := _q.ctx.Offset; offset != nil {
+ _spec.Offset = *offset
+ }
+ if ps := _q.order; len(ps) > 0 {
+ _spec.Order = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ return _spec
+}
+
+func (_q *PromoCodeQuery) sqlQuery(ctx context.Context) *sql.Selector {
+ builder := sql.Dialect(_q.driver.Dialect())
+ t1 := builder.Table(promocode.Table)
+ columns := _q.ctx.Fields
+ if len(columns) == 0 {
+ columns = promocode.Columns
+ }
+ selector := builder.Select(t1.Columns(columns...)...).From(t1)
+ if _q.sql != nil {
+ selector = _q.sql
+ selector.Select(selector.Columns(columns...)...)
+ }
+ if _q.ctx.Unique != nil && *_q.ctx.Unique {
+ selector.Distinct()
+ }
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
+ for _, p := range _q.predicates {
+ p(selector)
+ }
+ for _, p := range _q.order {
+ p(selector)
+ }
+ if offset := _q.ctx.Offset; offset != nil {
+ // limit is mandatory for offset clause. We start
+ // with default value, and override it below if needed.
+ selector.Offset(*offset).Limit(math.MaxInt32)
+ }
+ if limit := _q.ctx.Limit; limit != nil {
+ selector.Limit(*limit)
+ }
+ return selector
+}
+
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *PromoCodeQuery) ForUpdate(opts ...sql.LockOption) *PromoCodeQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *PromoCodeQuery) ForShare(opts ...sql.LockOption) *PromoCodeQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
+// PromoCodeGroupBy is the group-by builder for PromoCode entities.
+type PromoCodeGroupBy struct {
+ selector
+ build *PromoCodeQuery
+}
+
+// Aggregate adds the given aggregation functions to the group-by query.
+func (_g *PromoCodeGroupBy) Aggregate(fns ...AggregateFunc) *PromoCodeGroupBy {
+ _g.fns = append(_g.fns, fns...)
+ return _g
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (_g *PromoCodeGroupBy) Scan(ctx context.Context, v any) error {
+ ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
+ if err := _g.build.prepareQuery(ctx); err != nil {
+ return err
+ }
+ return scanWithInterceptors[*PromoCodeQuery, *PromoCodeGroupBy](ctx, _g.build, _g, _g.build.inters, v)
+}
+
+func (_g *PromoCodeGroupBy) sqlScan(ctx context.Context, root *PromoCodeQuery, v any) error {
+ selector := root.sqlQuery(ctx).Select()
+ aggregation := make([]string, 0, len(_g.fns))
+ for _, fn := range _g.fns {
+ aggregation = append(aggregation, fn(selector))
+ }
+ if len(selector.SelectedColumns()) == 0 {
+ columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
+ for _, f := range *_g.flds {
+ columns = append(columns, selector.C(f))
+ }
+ columns = append(columns, aggregation...)
+ selector.Select(columns...)
+ }
+ selector.GroupBy(selector.Columns(*_g.flds...)...)
+ if err := selector.Err(); err != nil {
+ return err
+ }
+ rows := &sql.Rows{}
+ query, args := selector.Query()
+ if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
+ return err
+ }
+ defer rows.Close()
+ return sql.ScanSlice(rows, v)
+}
+
+// PromoCodeSelect is the builder for selecting fields of PromoCode entities.
+type PromoCodeSelect struct {
+ *PromoCodeQuery
+ selector
+}
+
+// Aggregate adds the given aggregation functions to the selector query.
+func (_s *PromoCodeSelect) Aggregate(fns ...AggregateFunc) *PromoCodeSelect {
+ _s.fns = append(_s.fns, fns...)
+ return _s
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (_s *PromoCodeSelect) Scan(ctx context.Context, v any) error {
+ ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
+ if err := _s.prepareQuery(ctx); err != nil {
+ return err
+ }
+ return scanWithInterceptors[*PromoCodeQuery, *PromoCodeSelect](ctx, _s.PromoCodeQuery, _s, _s.inters, v)
+}
+
+func (_s *PromoCodeSelect) sqlScan(ctx context.Context, root *PromoCodeQuery, v any) error {
+ selector := root.sqlQuery(ctx)
+ aggregation := make([]string, 0, len(_s.fns))
+ for _, fn := range _s.fns {
+ aggregation = append(aggregation, fn(selector))
+ }
+ switch n := len(*_s.selector.flds); {
+ case n == 0 && len(aggregation) > 0:
+ selector.Select(aggregation...)
+ case n != 0 && len(aggregation) > 0:
+ selector.AppendSelect(aggregation...)
+ }
+ rows := &sql.Rows{}
+ query, args := selector.Query()
+ if err := _s.driver.Query(ctx, query, args, rows); err != nil {
+ return err
+ }
+ defer rows.Close()
+ return sql.ScanSlice(rows, v)
+}
diff --git a/backend/ent/promocode_update.go b/backend/ent/promocode_update.go
new file mode 100644
index 00000000..1a7481c8
--- /dev/null
+++ b/backend/ent/promocode_update.go
@@ -0,0 +1,745 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "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/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+)
+
+// PromoCodeUpdate is the builder for updating PromoCode entities.
+type PromoCodeUpdate struct {
+ config
+ hooks []Hook
+ mutation *PromoCodeMutation
+}
+
+// Where appends a list predicates to the PromoCodeUpdate builder.
+func (_u *PromoCodeUpdate) Where(ps ...predicate.PromoCode) *PromoCodeUpdate {
+ _u.mutation.Where(ps...)
+ return _u
+}
+
+// SetCode sets the "code" field.
+func (_u *PromoCodeUpdate) SetCode(v string) *PromoCodeUpdate {
+ _u.mutation.SetCode(v)
+ return _u
+}
+
+// SetNillableCode sets the "code" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableCode(v *string) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetCode(*v)
+ }
+ return _u
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (_u *PromoCodeUpdate) SetBonusAmount(v float64) *PromoCodeUpdate {
+ _u.mutation.ResetBonusAmount()
+ _u.mutation.SetBonusAmount(v)
+ return _u
+}
+
+// SetNillableBonusAmount sets the "bonus_amount" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableBonusAmount(v *float64) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetBonusAmount(*v)
+ }
+ return _u
+}
+
+// AddBonusAmount adds value to the "bonus_amount" field.
+func (_u *PromoCodeUpdate) AddBonusAmount(v float64) *PromoCodeUpdate {
+ _u.mutation.AddBonusAmount(v)
+ return _u
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (_u *PromoCodeUpdate) SetMaxUses(v int) *PromoCodeUpdate {
+ _u.mutation.ResetMaxUses()
+ _u.mutation.SetMaxUses(v)
+ return _u
+}
+
+// SetNillableMaxUses sets the "max_uses" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableMaxUses(v *int) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetMaxUses(*v)
+ }
+ return _u
+}
+
+// AddMaxUses adds value to the "max_uses" field.
+func (_u *PromoCodeUpdate) AddMaxUses(v int) *PromoCodeUpdate {
+ _u.mutation.AddMaxUses(v)
+ return _u
+}
+
+// SetUsedCount sets the "used_count" field.
+func (_u *PromoCodeUpdate) SetUsedCount(v int) *PromoCodeUpdate {
+ _u.mutation.ResetUsedCount()
+ _u.mutation.SetUsedCount(v)
+ return _u
+}
+
+// SetNillableUsedCount sets the "used_count" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableUsedCount(v *int) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetUsedCount(*v)
+ }
+ return _u
+}
+
+// AddUsedCount adds value to the "used_count" field.
+func (_u *PromoCodeUpdate) AddUsedCount(v int) *PromoCodeUpdate {
+ _u.mutation.AddUsedCount(v)
+ return _u
+}
+
+// SetStatus sets the "status" field.
+func (_u *PromoCodeUpdate) SetStatus(v string) *PromoCodeUpdate {
+ _u.mutation.SetStatus(v)
+ return _u
+}
+
+// SetNillableStatus sets the "status" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableStatus(v *string) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetStatus(*v)
+ }
+ return _u
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (_u *PromoCodeUpdate) SetExpiresAt(v time.Time) *PromoCodeUpdate {
+ _u.mutation.SetExpiresAt(v)
+ return _u
+}
+
+// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableExpiresAt(v *time.Time) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetExpiresAt(*v)
+ }
+ return _u
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (_u *PromoCodeUpdate) ClearExpiresAt() *PromoCodeUpdate {
+ _u.mutation.ClearExpiresAt()
+ return _u
+}
+
+// SetNotes sets the "notes" field.
+func (_u *PromoCodeUpdate) SetNotes(v string) *PromoCodeUpdate {
+ _u.mutation.SetNotes(v)
+ return _u
+}
+
+// SetNillableNotes sets the "notes" field if the given value is not nil.
+func (_u *PromoCodeUpdate) SetNillableNotes(v *string) *PromoCodeUpdate {
+ if v != nil {
+ _u.SetNotes(*v)
+ }
+ return _u
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (_u *PromoCodeUpdate) ClearNotes() *PromoCodeUpdate {
+ _u.mutation.ClearNotes()
+ return _u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_u *PromoCodeUpdate) SetUpdatedAt(v time.Time) *PromoCodeUpdate {
+ _u.mutation.SetUpdatedAt(v)
+ return _u
+}
+
+// AddUsageRecordIDs adds the "usage_records" edge to the PromoCodeUsage entity by IDs.
+func (_u *PromoCodeUpdate) AddUsageRecordIDs(ids ...int64) *PromoCodeUpdate {
+ _u.mutation.AddUsageRecordIDs(ids...)
+ return _u
+}
+
+// AddUsageRecords adds the "usage_records" edges to the PromoCodeUsage entity.
+func (_u *PromoCodeUpdate) AddUsageRecords(v ...*PromoCodeUsage) *PromoCodeUpdate {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.AddUsageRecordIDs(ids...)
+}
+
+// Mutation returns the PromoCodeMutation object of the builder.
+func (_u *PromoCodeUpdate) Mutation() *PromoCodeMutation {
+ return _u.mutation
+}
+
+// ClearUsageRecords clears all "usage_records" edges to the PromoCodeUsage entity.
+func (_u *PromoCodeUpdate) ClearUsageRecords() *PromoCodeUpdate {
+ _u.mutation.ClearUsageRecords()
+ return _u
+}
+
+// RemoveUsageRecordIDs removes the "usage_records" edge to PromoCodeUsage entities by IDs.
+func (_u *PromoCodeUpdate) RemoveUsageRecordIDs(ids ...int64) *PromoCodeUpdate {
+ _u.mutation.RemoveUsageRecordIDs(ids...)
+ return _u
+}
+
+// RemoveUsageRecords removes "usage_records" edges to PromoCodeUsage entities.
+func (_u *PromoCodeUpdate) RemoveUsageRecords(v ...*PromoCodeUsage) *PromoCodeUpdate {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.RemoveUsageRecordIDs(ids...)
+}
+
+// Save executes the query and returns the number of nodes affected by the update operation.
+func (_u *PromoCodeUpdate) Save(ctx context.Context) (int, error) {
+ _u.defaults()
+ return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_u *PromoCodeUpdate) SaveX(ctx context.Context) int {
+ affected, err := _u.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return affected
+}
+
+// Exec executes the query.
+func (_u *PromoCodeUpdate) Exec(ctx context.Context) error {
+ _, err := _u.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_u *PromoCodeUpdate) ExecX(ctx context.Context) {
+ if err := _u.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (_u *PromoCodeUpdate) defaults() {
+ if _, ok := _u.mutation.UpdatedAt(); !ok {
+ v := promocode.UpdateDefaultUpdatedAt()
+ _u.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_u *PromoCodeUpdate) check() error {
+ if v, ok := _u.mutation.Code(); ok {
+ if err := promocode.CodeValidator(v); err != nil {
+ return &ValidationError{Name: "code", err: fmt.Errorf(`ent: validator failed for field "PromoCode.code": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.Status(); ok {
+ if err := promocode.StatusValidator(v); err != nil {
+ return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PromoCode.status": %w`, err)}
+ }
+ }
+ return nil
+}
+
+func (_u *PromoCodeUpdate) sqlSave(ctx context.Context) (_node int, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(promocode.Table, promocode.Columns, sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64))
+ if ps := _u.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if value, ok := _u.mutation.Code(); ok {
+ _spec.SetField(promocode.FieldCode, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.BonusAmount(); ok {
+ _spec.SetField(promocode.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedBonusAmount(); ok {
+ _spec.AddField(promocode.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.MaxUses(); ok {
+ _spec.SetField(promocode.FieldMaxUses, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.AddedMaxUses(); ok {
+ _spec.AddField(promocode.FieldMaxUses, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.UsedCount(); ok {
+ _spec.SetField(promocode.FieldUsedCount, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.AddedUsedCount(); ok {
+ _spec.AddField(promocode.FieldUsedCount, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.Status(); ok {
+ _spec.SetField(promocode.FieldStatus, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.ExpiresAt(); ok {
+ _spec.SetField(promocode.FieldExpiresAt, field.TypeTime, value)
+ }
+ if _u.mutation.ExpiresAtCleared() {
+ _spec.ClearField(promocode.FieldExpiresAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.Notes(); ok {
+ _spec.SetField(promocode.FieldNotes, field.TypeString, value)
+ }
+ if _u.mutation.NotesCleared() {
+ _spec.ClearField(promocode.FieldNotes, field.TypeString)
+ }
+ if value, ok := _u.mutation.UpdatedAt(); ok {
+ _spec.SetField(promocode.FieldUpdatedAt, field.TypeTime, value)
+ }
+ if _u.mutation.UsageRecordsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.RemovedUsageRecordsIDs(); len(nodes) > 0 && !_u.mutation.UsageRecordsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.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.UsageRecordsIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.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{promocode.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return 0, err
+ }
+ _u.mutation.done = true
+ return _node, nil
+}
+
+// PromoCodeUpdateOne is the builder for updating a single PromoCode entity.
+type PromoCodeUpdateOne struct {
+ config
+ fields []string
+ hooks []Hook
+ mutation *PromoCodeMutation
+}
+
+// SetCode sets the "code" field.
+func (_u *PromoCodeUpdateOne) SetCode(v string) *PromoCodeUpdateOne {
+ _u.mutation.SetCode(v)
+ return _u
+}
+
+// SetNillableCode sets the "code" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableCode(v *string) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetCode(*v)
+ }
+ return _u
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (_u *PromoCodeUpdateOne) SetBonusAmount(v float64) *PromoCodeUpdateOne {
+ _u.mutation.ResetBonusAmount()
+ _u.mutation.SetBonusAmount(v)
+ return _u
+}
+
+// SetNillableBonusAmount sets the "bonus_amount" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableBonusAmount(v *float64) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetBonusAmount(*v)
+ }
+ return _u
+}
+
+// AddBonusAmount adds value to the "bonus_amount" field.
+func (_u *PromoCodeUpdateOne) AddBonusAmount(v float64) *PromoCodeUpdateOne {
+ _u.mutation.AddBonusAmount(v)
+ return _u
+}
+
+// SetMaxUses sets the "max_uses" field.
+func (_u *PromoCodeUpdateOne) SetMaxUses(v int) *PromoCodeUpdateOne {
+ _u.mutation.ResetMaxUses()
+ _u.mutation.SetMaxUses(v)
+ return _u
+}
+
+// SetNillableMaxUses sets the "max_uses" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableMaxUses(v *int) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetMaxUses(*v)
+ }
+ return _u
+}
+
+// AddMaxUses adds value to the "max_uses" field.
+func (_u *PromoCodeUpdateOne) AddMaxUses(v int) *PromoCodeUpdateOne {
+ _u.mutation.AddMaxUses(v)
+ return _u
+}
+
+// SetUsedCount sets the "used_count" field.
+func (_u *PromoCodeUpdateOne) SetUsedCount(v int) *PromoCodeUpdateOne {
+ _u.mutation.ResetUsedCount()
+ _u.mutation.SetUsedCount(v)
+ return _u
+}
+
+// SetNillableUsedCount sets the "used_count" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableUsedCount(v *int) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetUsedCount(*v)
+ }
+ return _u
+}
+
+// AddUsedCount adds value to the "used_count" field.
+func (_u *PromoCodeUpdateOne) AddUsedCount(v int) *PromoCodeUpdateOne {
+ _u.mutation.AddUsedCount(v)
+ return _u
+}
+
+// SetStatus sets the "status" field.
+func (_u *PromoCodeUpdateOne) SetStatus(v string) *PromoCodeUpdateOne {
+ _u.mutation.SetStatus(v)
+ return _u
+}
+
+// SetNillableStatus sets the "status" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableStatus(v *string) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetStatus(*v)
+ }
+ return _u
+}
+
+// SetExpiresAt sets the "expires_at" field.
+func (_u *PromoCodeUpdateOne) SetExpiresAt(v time.Time) *PromoCodeUpdateOne {
+ _u.mutation.SetExpiresAt(v)
+ return _u
+}
+
+// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableExpiresAt(v *time.Time) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetExpiresAt(*v)
+ }
+ return _u
+}
+
+// ClearExpiresAt clears the value of the "expires_at" field.
+func (_u *PromoCodeUpdateOne) ClearExpiresAt() *PromoCodeUpdateOne {
+ _u.mutation.ClearExpiresAt()
+ return _u
+}
+
+// SetNotes sets the "notes" field.
+func (_u *PromoCodeUpdateOne) SetNotes(v string) *PromoCodeUpdateOne {
+ _u.mutation.SetNotes(v)
+ return _u
+}
+
+// SetNillableNotes sets the "notes" field if the given value is not nil.
+func (_u *PromoCodeUpdateOne) SetNillableNotes(v *string) *PromoCodeUpdateOne {
+ if v != nil {
+ _u.SetNotes(*v)
+ }
+ return _u
+}
+
+// ClearNotes clears the value of the "notes" field.
+func (_u *PromoCodeUpdateOne) ClearNotes() *PromoCodeUpdateOne {
+ _u.mutation.ClearNotes()
+ return _u
+}
+
+// SetUpdatedAt sets the "updated_at" field.
+func (_u *PromoCodeUpdateOne) SetUpdatedAt(v time.Time) *PromoCodeUpdateOne {
+ _u.mutation.SetUpdatedAt(v)
+ return _u
+}
+
+// AddUsageRecordIDs adds the "usage_records" edge to the PromoCodeUsage entity by IDs.
+func (_u *PromoCodeUpdateOne) AddUsageRecordIDs(ids ...int64) *PromoCodeUpdateOne {
+ _u.mutation.AddUsageRecordIDs(ids...)
+ return _u
+}
+
+// AddUsageRecords adds the "usage_records" edges to the PromoCodeUsage entity.
+func (_u *PromoCodeUpdateOne) AddUsageRecords(v ...*PromoCodeUsage) *PromoCodeUpdateOne {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.AddUsageRecordIDs(ids...)
+}
+
+// Mutation returns the PromoCodeMutation object of the builder.
+func (_u *PromoCodeUpdateOne) Mutation() *PromoCodeMutation {
+ return _u.mutation
+}
+
+// ClearUsageRecords clears all "usage_records" edges to the PromoCodeUsage entity.
+func (_u *PromoCodeUpdateOne) ClearUsageRecords() *PromoCodeUpdateOne {
+ _u.mutation.ClearUsageRecords()
+ return _u
+}
+
+// RemoveUsageRecordIDs removes the "usage_records" edge to PromoCodeUsage entities by IDs.
+func (_u *PromoCodeUpdateOne) RemoveUsageRecordIDs(ids ...int64) *PromoCodeUpdateOne {
+ _u.mutation.RemoveUsageRecordIDs(ids...)
+ return _u
+}
+
+// RemoveUsageRecords removes "usage_records" edges to PromoCodeUsage entities.
+func (_u *PromoCodeUpdateOne) RemoveUsageRecords(v ...*PromoCodeUsage) *PromoCodeUpdateOne {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.RemoveUsageRecordIDs(ids...)
+}
+
+// Where appends a list predicates to the PromoCodeUpdate builder.
+func (_u *PromoCodeUpdateOne) Where(ps ...predicate.PromoCode) *PromoCodeUpdateOne {
+ _u.mutation.Where(ps...)
+ return _u
+}
+
+// Select allows selecting one or more fields (columns) of the returned entity.
+// The default is selecting all fields defined in the entity schema.
+func (_u *PromoCodeUpdateOne) Select(field string, fields ...string) *PromoCodeUpdateOne {
+ _u.fields = append([]string{field}, fields...)
+ return _u
+}
+
+// Save executes the query and returns the updated PromoCode entity.
+func (_u *PromoCodeUpdateOne) Save(ctx context.Context) (*PromoCode, error) {
+ _u.defaults()
+ return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_u *PromoCodeUpdateOne) SaveX(ctx context.Context) *PromoCode {
+ node, err := _u.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// Exec executes the query on the entity.
+func (_u *PromoCodeUpdateOne) Exec(ctx context.Context) error {
+ _, err := _u.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_u *PromoCodeUpdateOne) ExecX(ctx context.Context) {
+ if err := _u.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (_u *PromoCodeUpdateOne) defaults() {
+ if _, ok := _u.mutation.UpdatedAt(); !ok {
+ v := promocode.UpdateDefaultUpdatedAt()
+ _u.mutation.SetUpdatedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_u *PromoCodeUpdateOne) check() error {
+ if v, ok := _u.mutation.Code(); ok {
+ if err := promocode.CodeValidator(v); err != nil {
+ return &ValidationError{Name: "code", err: fmt.Errorf(`ent: validator failed for field "PromoCode.code": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.Status(); ok {
+ if err := promocode.StatusValidator(v); err != nil {
+ return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PromoCode.status": %w`, err)}
+ }
+ }
+ return nil
+}
+
+func (_u *PromoCodeUpdateOne) sqlSave(ctx context.Context) (_node *PromoCode, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(promocode.Table, promocode.Columns, sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64))
+ id, ok := _u.mutation.ID()
+ if !ok {
+ return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PromoCode.id" for update`)}
+ }
+ _spec.Node.ID.Value = id
+ if fields := _u.fields; len(fields) > 0 {
+ _spec.Node.Columns = make([]string, 0, len(fields))
+ _spec.Node.Columns = append(_spec.Node.Columns, promocode.FieldID)
+ for _, f := range fields {
+ if !promocode.ValidColumn(f) {
+ return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ if f != promocode.FieldID {
+ _spec.Node.Columns = append(_spec.Node.Columns, f)
+ }
+ }
+ }
+ if ps := _u.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if value, ok := _u.mutation.Code(); ok {
+ _spec.SetField(promocode.FieldCode, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.BonusAmount(); ok {
+ _spec.SetField(promocode.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedBonusAmount(); ok {
+ _spec.AddField(promocode.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.MaxUses(); ok {
+ _spec.SetField(promocode.FieldMaxUses, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.AddedMaxUses(); ok {
+ _spec.AddField(promocode.FieldMaxUses, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.UsedCount(); ok {
+ _spec.SetField(promocode.FieldUsedCount, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.AddedUsedCount(); ok {
+ _spec.AddField(promocode.FieldUsedCount, field.TypeInt, value)
+ }
+ if value, ok := _u.mutation.Status(); ok {
+ _spec.SetField(promocode.FieldStatus, field.TypeString, value)
+ }
+ if value, ok := _u.mutation.ExpiresAt(); ok {
+ _spec.SetField(promocode.FieldExpiresAt, field.TypeTime, value)
+ }
+ if _u.mutation.ExpiresAtCleared() {
+ _spec.ClearField(promocode.FieldExpiresAt, field.TypeTime)
+ }
+ if value, ok := _u.mutation.Notes(); ok {
+ _spec.SetField(promocode.FieldNotes, field.TypeString, value)
+ }
+ if _u.mutation.NotesCleared() {
+ _spec.ClearField(promocode.FieldNotes, field.TypeString)
+ }
+ if value, ok := _u.mutation.UpdatedAt(); ok {
+ _spec.SetField(promocode.FieldUpdatedAt, field.TypeTime, value)
+ }
+ if _u.mutation.UsageRecordsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.RemovedUsageRecordsIDs(); len(nodes) > 0 && !_u.mutation.UsageRecordsCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.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.UsageRecordsIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: promocode.UsageRecordsTable,
+ Columns: []string{promocode.UsageRecordsColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges.Add = append(_spec.Edges.Add, edge)
+ }
+ _node = &PromoCode{config: _u.config}
+ _spec.Assign = _node.assignValues
+ _spec.ScanValues = _node.scanValues
+ if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
+ if _, ok := err.(*sqlgraph.NotFoundError); ok {
+ err = &NotFoundError{promocode.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return nil, err
+ }
+ _u.mutation.done = true
+ return _node, nil
+}
diff --git a/backend/ent/promocodeusage.go b/backend/ent/promocodeusage.go
new file mode 100644
index 00000000..1ba3a8bf
--- /dev/null
+++ b/backend/ent/promocodeusage.go
@@ -0,0 +1,187 @@
+// 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/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+ "github.com/Wei-Shaw/sub2api/ent/user"
+)
+
+// PromoCodeUsage is the model entity for the PromoCodeUsage schema.
+type PromoCodeUsage struct {
+ config `json:"-"`
+ // ID of the ent.
+ ID int64 `json:"id,omitempty"`
+ // 优惠码ID
+ PromoCodeID int64 `json:"promo_code_id,omitempty"`
+ // 使用用户ID
+ UserID int64 `json:"user_id,omitempty"`
+ // 实际赠送金额
+ BonusAmount float64 `json:"bonus_amount,omitempty"`
+ // 使用时间
+ UsedAt time.Time `json:"used_at,omitempty"`
+ // Edges holds the relations/edges for other nodes in the graph.
+ // The values are being populated by the PromoCodeUsageQuery when eager-loading is set.
+ Edges PromoCodeUsageEdges `json:"edges"`
+ selectValues sql.SelectValues
+}
+
+// PromoCodeUsageEdges holds the relations/edges for other nodes in the graph.
+type PromoCodeUsageEdges struct {
+ // PromoCode holds the value of the promo_code edge.
+ PromoCode *PromoCode `json:"promo_code,omitempty"`
+ // User holds the value of the user edge.
+ User *User `json:"user,omitempty"`
+ // loadedTypes holds the information for reporting if a
+ // type was loaded (or requested) in eager-loading or not.
+ loadedTypes [2]bool
+}
+
+// PromoCodeOrErr returns the PromoCode value or an error if the edge
+// was not loaded in eager-loading, or loaded but was not found.
+func (e PromoCodeUsageEdges) PromoCodeOrErr() (*PromoCode, error) {
+ if e.PromoCode != nil {
+ return e.PromoCode, nil
+ } else if e.loadedTypes[0] {
+ return nil, &NotFoundError{label: promocode.Label}
+ }
+ return nil, &NotLoadedError{edge: "promo_code"}
+}
+
+// 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 PromoCodeUsageEdges) UserOrErr() (*User, error) {
+ if e.User != nil {
+ return e.User, nil
+ } else if e.loadedTypes[1] {
+ return nil, &NotFoundError{label: user.Label}
+ }
+ return nil, &NotLoadedError{edge: "user"}
+}
+
+// scanValues returns the types for scanning values from sql.Rows.
+func (*PromoCodeUsage) scanValues(columns []string) ([]any, error) {
+ values := make([]any, len(columns))
+ for i := range columns {
+ switch columns[i] {
+ case promocodeusage.FieldBonusAmount:
+ values[i] = new(sql.NullFloat64)
+ case promocodeusage.FieldID, promocodeusage.FieldPromoCodeID, promocodeusage.FieldUserID:
+ values[i] = new(sql.NullInt64)
+ case promocodeusage.FieldUsedAt:
+ 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 PromoCodeUsage fields.
+func (_m *PromoCodeUsage) 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 promocodeusage.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 promocodeusage.FieldPromoCodeID:
+ if value, ok := values[i].(*sql.NullInt64); !ok {
+ return fmt.Errorf("unexpected type %T for field promo_code_id", values[i])
+ } else if value.Valid {
+ _m.PromoCodeID = value.Int64
+ }
+ case promocodeusage.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 promocodeusage.FieldBonusAmount:
+ if value, ok := values[i].(*sql.NullFloat64); !ok {
+ return fmt.Errorf("unexpected type %T for field bonus_amount", values[i])
+ } else if value.Valid {
+ _m.BonusAmount = value.Float64
+ }
+ case promocodeusage.FieldUsedAt:
+ if value, ok := values[i].(*sql.NullTime); !ok {
+ return fmt.Errorf("unexpected type %T for field used_at", values[i])
+ } else if value.Valid {
+ _m.UsedAt = 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 PromoCodeUsage.
+// This includes values selected through modifiers, order, etc.
+func (_m *PromoCodeUsage) Value(name string) (ent.Value, error) {
+ return _m.selectValues.Get(name)
+}
+
+// QueryPromoCode queries the "promo_code" edge of the PromoCodeUsage entity.
+func (_m *PromoCodeUsage) QueryPromoCode() *PromoCodeQuery {
+ return NewPromoCodeUsageClient(_m.config).QueryPromoCode(_m)
+}
+
+// QueryUser queries the "user" edge of the PromoCodeUsage entity.
+func (_m *PromoCodeUsage) QueryUser() *UserQuery {
+ return NewPromoCodeUsageClient(_m.config).QueryUser(_m)
+}
+
+// Update returns a builder for updating this PromoCodeUsage.
+// Note that you need to call PromoCodeUsage.Unwrap() before calling this method if this PromoCodeUsage
+// was returned from a transaction, and the transaction was committed or rolled back.
+func (_m *PromoCodeUsage) Update() *PromoCodeUsageUpdateOne {
+ return NewPromoCodeUsageClient(_m.config).UpdateOne(_m)
+}
+
+// Unwrap unwraps the PromoCodeUsage 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 *PromoCodeUsage) Unwrap() *PromoCodeUsage {
+ _tx, ok := _m.config.driver.(*txDriver)
+ if !ok {
+ panic("ent: PromoCodeUsage is not a transactional entity")
+ }
+ _m.config.driver = _tx.drv
+ return _m
+}
+
+// String implements the fmt.Stringer.
+func (_m *PromoCodeUsage) String() string {
+ var builder strings.Builder
+ builder.WriteString("PromoCodeUsage(")
+ builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
+ builder.WriteString("promo_code_id=")
+ builder.WriteString(fmt.Sprintf("%v", _m.PromoCodeID))
+ builder.WriteString(", ")
+ builder.WriteString("user_id=")
+ builder.WriteString(fmt.Sprintf("%v", _m.UserID))
+ builder.WriteString(", ")
+ builder.WriteString("bonus_amount=")
+ builder.WriteString(fmt.Sprintf("%v", _m.BonusAmount))
+ builder.WriteString(", ")
+ builder.WriteString("used_at=")
+ builder.WriteString(_m.UsedAt.Format(time.ANSIC))
+ builder.WriteByte(')')
+ return builder.String()
+}
+
+// PromoCodeUsages is a parsable slice of PromoCodeUsage.
+type PromoCodeUsages []*PromoCodeUsage
diff --git a/backend/ent/promocodeusage/promocodeusage.go b/backend/ent/promocodeusage/promocodeusage.go
new file mode 100644
index 00000000..f4e05970
--- /dev/null
+++ b/backend/ent/promocodeusage/promocodeusage.go
@@ -0,0 +1,125 @@
+// Code generated by ent, DO NOT EDIT.
+
+package promocodeusage
+
+import (
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+)
+
+const (
+ // Label holds the string label denoting the promocodeusage type in the database.
+ Label = "promo_code_usage"
+ // FieldID holds the string denoting the id field in the database.
+ FieldID = "id"
+ // FieldPromoCodeID holds the string denoting the promo_code_id field in the database.
+ FieldPromoCodeID = "promo_code_id"
+ // FieldUserID holds the string denoting the user_id field in the database.
+ FieldUserID = "user_id"
+ // FieldBonusAmount holds the string denoting the bonus_amount field in the database.
+ FieldBonusAmount = "bonus_amount"
+ // FieldUsedAt holds the string denoting the used_at field in the database.
+ FieldUsedAt = "used_at"
+ // EdgePromoCode holds the string denoting the promo_code edge name in mutations.
+ EdgePromoCode = "promo_code"
+ // EdgeUser holds the string denoting the user edge name in mutations.
+ EdgeUser = "user"
+ // Table holds the table name of the promocodeusage in the database.
+ Table = "promo_code_usages"
+ // PromoCodeTable is the table that holds the promo_code relation/edge.
+ PromoCodeTable = "promo_code_usages"
+ // PromoCodeInverseTable is the table name for the PromoCode entity.
+ // It exists in this package in order to avoid circular dependency with the "promocode" package.
+ PromoCodeInverseTable = "promo_codes"
+ // PromoCodeColumn is the table column denoting the promo_code relation/edge.
+ PromoCodeColumn = "promo_code_id"
+ // UserTable is the table that holds the user relation/edge.
+ UserTable = "promo_code_usages"
+ // UserInverseTable is the table name for the User entity.
+ // It exists in this package in order to avoid circular dependency with the "user" package.
+ UserInverseTable = "users"
+ // UserColumn is the table column denoting the user relation/edge.
+ UserColumn = "user_id"
+)
+
+// Columns holds all SQL columns for promocodeusage fields.
+var Columns = []string{
+ FieldID,
+ FieldPromoCodeID,
+ FieldUserID,
+ FieldBonusAmount,
+ FieldUsedAt,
+}
+
+// ValidColumn reports if the column name is valid (part of the table columns).
+func ValidColumn(column string) bool {
+ for i := range Columns {
+ if column == Columns[i] {
+ return true
+ }
+ }
+ return false
+}
+
+var (
+ // DefaultUsedAt holds the default value on creation for the "used_at" field.
+ DefaultUsedAt func() time.Time
+)
+
+// OrderOption defines the ordering options for the PromoCodeUsage queries.
+type OrderOption func(*sql.Selector)
+
+// ByID orders the results by the id field.
+func ByID(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldID, opts...).ToFunc()
+}
+
+// ByPromoCodeID orders the results by the promo_code_id field.
+func ByPromoCodeID(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldPromoCodeID, opts...).ToFunc()
+}
+
+// ByUserID orders the results by the user_id field.
+func ByUserID(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldUserID, opts...).ToFunc()
+}
+
+// ByBonusAmount orders the results by the bonus_amount field.
+func ByBonusAmount(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldBonusAmount, opts...).ToFunc()
+}
+
+// ByUsedAt orders the results by the used_at field.
+func ByUsedAt(opts ...sql.OrderTermOption) OrderOption {
+ return sql.OrderByField(FieldUsedAt, opts...).ToFunc()
+}
+
+// ByPromoCodeField orders the results by promo_code field.
+func ByPromoCodeField(field string, opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newPromoCodeStep(), sql.OrderByField(field, opts...))
+ }
+}
+
+// ByUserField orders the results by user field.
+func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
+ }
+}
+func newPromoCodeStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(PromoCodeInverseTable, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, PromoCodeTable, PromoCodeColumn),
+ )
+}
+func newUserStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(UserInverseTable, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
+ )
+}
diff --git a/backend/ent/promocodeusage/where.go b/backend/ent/promocodeusage/where.go
new file mode 100644
index 00000000..fe657fd4
--- /dev/null
+++ b/backend/ent/promocodeusage/where.go
@@ -0,0 +1,257 @@
+// Code generated by ent, DO NOT EDIT.
+
+package promocodeusage
+
+import (
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+ "github.com/Wei-Shaw/sub2api/ent/predicate"
+)
+
+// ID filters vertices based on their ID field.
+func ID(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldID, id))
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldID, id))
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNEQ(FieldID, id))
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldIn(FieldID, ids...))
+}
+
+// IDNotIn applies the NotIn predicate on the ID field.
+func IDNotIn(ids ...int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNotIn(FieldID, ids...))
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldGT(FieldID, id))
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldGTE(FieldID, id))
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldLT(FieldID, id))
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldLTE(FieldID, id))
+}
+
+// PromoCodeID applies equality check predicate on the "promo_code_id" field. It's identical to PromoCodeIDEQ.
+func PromoCodeID(v int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldPromoCodeID, v))
+}
+
+// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ.
+func UserID(v int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldUserID, v))
+}
+
+// BonusAmount applies equality check predicate on the "bonus_amount" field. It's identical to BonusAmountEQ.
+func BonusAmount(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldBonusAmount, v))
+}
+
+// UsedAt applies equality check predicate on the "used_at" field. It's identical to UsedAtEQ.
+func UsedAt(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldUsedAt, v))
+}
+
+// PromoCodeIDEQ applies the EQ predicate on the "promo_code_id" field.
+func PromoCodeIDEQ(v int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldPromoCodeID, v))
+}
+
+// PromoCodeIDNEQ applies the NEQ predicate on the "promo_code_id" field.
+func PromoCodeIDNEQ(v int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNEQ(FieldPromoCodeID, v))
+}
+
+// PromoCodeIDIn applies the In predicate on the "promo_code_id" field.
+func PromoCodeIDIn(vs ...int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldIn(FieldPromoCodeID, vs...))
+}
+
+// PromoCodeIDNotIn applies the NotIn predicate on the "promo_code_id" field.
+func PromoCodeIDNotIn(vs ...int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNotIn(FieldPromoCodeID, vs...))
+}
+
+// UserIDEQ applies the EQ predicate on the "user_id" field.
+func UserIDEQ(v int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldUserID, v))
+}
+
+// UserIDNEQ applies the NEQ predicate on the "user_id" field.
+func UserIDNEQ(v int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNEQ(FieldUserID, v))
+}
+
+// UserIDIn applies the In predicate on the "user_id" field.
+func UserIDIn(vs ...int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldIn(FieldUserID, vs...))
+}
+
+// UserIDNotIn applies the NotIn predicate on the "user_id" field.
+func UserIDNotIn(vs ...int64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNotIn(FieldUserID, vs...))
+}
+
+// BonusAmountEQ applies the EQ predicate on the "bonus_amount" field.
+func BonusAmountEQ(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldBonusAmount, v))
+}
+
+// BonusAmountNEQ applies the NEQ predicate on the "bonus_amount" field.
+func BonusAmountNEQ(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNEQ(FieldBonusAmount, v))
+}
+
+// BonusAmountIn applies the In predicate on the "bonus_amount" field.
+func BonusAmountIn(vs ...float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldIn(FieldBonusAmount, vs...))
+}
+
+// BonusAmountNotIn applies the NotIn predicate on the "bonus_amount" field.
+func BonusAmountNotIn(vs ...float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNotIn(FieldBonusAmount, vs...))
+}
+
+// BonusAmountGT applies the GT predicate on the "bonus_amount" field.
+func BonusAmountGT(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldGT(FieldBonusAmount, v))
+}
+
+// BonusAmountGTE applies the GTE predicate on the "bonus_amount" field.
+func BonusAmountGTE(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldGTE(FieldBonusAmount, v))
+}
+
+// BonusAmountLT applies the LT predicate on the "bonus_amount" field.
+func BonusAmountLT(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldLT(FieldBonusAmount, v))
+}
+
+// BonusAmountLTE applies the LTE predicate on the "bonus_amount" field.
+func BonusAmountLTE(v float64) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldLTE(FieldBonusAmount, v))
+}
+
+// UsedAtEQ applies the EQ predicate on the "used_at" field.
+func UsedAtEQ(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldEQ(FieldUsedAt, v))
+}
+
+// UsedAtNEQ applies the NEQ predicate on the "used_at" field.
+func UsedAtNEQ(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNEQ(FieldUsedAt, v))
+}
+
+// UsedAtIn applies the In predicate on the "used_at" field.
+func UsedAtIn(vs ...time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldIn(FieldUsedAt, vs...))
+}
+
+// UsedAtNotIn applies the NotIn predicate on the "used_at" field.
+func UsedAtNotIn(vs ...time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldNotIn(FieldUsedAt, vs...))
+}
+
+// UsedAtGT applies the GT predicate on the "used_at" field.
+func UsedAtGT(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldGT(FieldUsedAt, v))
+}
+
+// UsedAtGTE applies the GTE predicate on the "used_at" field.
+func UsedAtGTE(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldGTE(FieldUsedAt, v))
+}
+
+// UsedAtLT applies the LT predicate on the "used_at" field.
+func UsedAtLT(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldLT(FieldUsedAt, v))
+}
+
+// UsedAtLTE applies the LTE predicate on the "used_at" field.
+func UsedAtLTE(v time.Time) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.FieldLTE(FieldUsedAt, v))
+}
+
+// HasPromoCode applies the HasEdge predicate on the "promo_code" edge.
+func HasPromoCode() predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, PromoCodeTable, PromoCodeColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasPromoCodeWith applies the HasEdge predicate on the "promo_code" edge with a given conditions (other predicates).
+func HasPromoCodeWith(preds ...predicate.PromoCode) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(func(s *sql.Selector) {
+ step := newPromoCodeStep()
+ sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
+ for _, p := range preds {
+ p(s)
+ }
+ })
+ })
+}
+
+// HasUser applies the HasEdge predicate on the "user" edge.
+func HasUser() predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates).
+func HasUserWith(preds ...predicate.User) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(func(s *sql.Selector) {
+ step := newUserStep()
+ 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.PromoCodeUsage) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.AndPredicates(predicates...))
+}
+
+// Or groups predicates with the OR operator between them.
+func Or(predicates ...predicate.PromoCodeUsage) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.OrPredicates(predicates...))
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.PromoCodeUsage) predicate.PromoCodeUsage {
+ return predicate.PromoCodeUsage(sql.NotPredicates(p))
+}
diff --git a/backend/ent/promocodeusage_create.go b/backend/ent/promocodeusage_create.go
new file mode 100644
index 00000000..79d9c768
--- /dev/null
+++ b/backend/ent/promocodeusage_create.go
@@ -0,0 +1,696 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "entgo.io/ent/dialect/sql"
+ "entgo.io/ent/dialect/sql/sqlgraph"
+ "entgo.io/ent/schema/field"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+ "github.com/Wei-Shaw/sub2api/ent/user"
+)
+
+// PromoCodeUsageCreate is the builder for creating a PromoCodeUsage entity.
+type PromoCodeUsageCreate struct {
+ config
+ mutation *PromoCodeUsageMutation
+ hooks []Hook
+ conflict []sql.ConflictOption
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (_c *PromoCodeUsageCreate) SetPromoCodeID(v int64) *PromoCodeUsageCreate {
+ _c.mutation.SetPromoCodeID(v)
+ return _c
+}
+
+// SetUserID sets the "user_id" field.
+func (_c *PromoCodeUsageCreate) SetUserID(v int64) *PromoCodeUsageCreate {
+ _c.mutation.SetUserID(v)
+ return _c
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (_c *PromoCodeUsageCreate) SetBonusAmount(v float64) *PromoCodeUsageCreate {
+ _c.mutation.SetBonusAmount(v)
+ return _c
+}
+
+// SetUsedAt sets the "used_at" field.
+func (_c *PromoCodeUsageCreate) SetUsedAt(v time.Time) *PromoCodeUsageCreate {
+ _c.mutation.SetUsedAt(v)
+ return _c
+}
+
+// SetNillableUsedAt sets the "used_at" field if the given value is not nil.
+func (_c *PromoCodeUsageCreate) SetNillableUsedAt(v *time.Time) *PromoCodeUsageCreate {
+ if v != nil {
+ _c.SetUsedAt(*v)
+ }
+ return _c
+}
+
+// SetPromoCode sets the "promo_code" edge to the PromoCode entity.
+func (_c *PromoCodeUsageCreate) SetPromoCode(v *PromoCode) *PromoCodeUsageCreate {
+ return _c.SetPromoCodeID(v.ID)
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (_c *PromoCodeUsageCreate) SetUser(v *User) *PromoCodeUsageCreate {
+ return _c.SetUserID(v.ID)
+}
+
+// Mutation returns the PromoCodeUsageMutation object of the builder.
+func (_c *PromoCodeUsageCreate) Mutation() *PromoCodeUsageMutation {
+ return _c.mutation
+}
+
+// Save creates the PromoCodeUsage in the database.
+func (_c *PromoCodeUsageCreate) Save(ctx context.Context) (*PromoCodeUsage, error) {
+ _c.defaults()
+ return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (_c *PromoCodeUsageCreate) SaveX(ctx context.Context) *PromoCodeUsage {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PromoCodeUsageCreate) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PromoCodeUsageCreate) ExecX(ctx context.Context) {
+ if err := _c.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// defaults sets the default values of the builder before save.
+func (_c *PromoCodeUsageCreate) defaults() {
+ if _, ok := _c.mutation.UsedAt(); !ok {
+ v := promocodeusage.DefaultUsedAt()
+ _c.mutation.SetUsedAt(v)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_c *PromoCodeUsageCreate) check() error {
+ if _, ok := _c.mutation.PromoCodeID(); !ok {
+ return &ValidationError{Name: "promo_code_id", err: errors.New(`ent: missing required field "PromoCodeUsage.promo_code_id"`)}
+ }
+ if _, ok := _c.mutation.UserID(); !ok {
+ return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "PromoCodeUsage.user_id"`)}
+ }
+ if _, ok := _c.mutation.BonusAmount(); !ok {
+ return &ValidationError{Name: "bonus_amount", err: errors.New(`ent: missing required field "PromoCodeUsage.bonus_amount"`)}
+ }
+ if _, ok := _c.mutation.UsedAt(); !ok {
+ return &ValidationError{Name: "used_at", err: errors.New(`ent: missing required field "PromoCodeUsage.used_at"`)}
+ }
+ if len(_c.mutation.PromoCodeIDs()) == 0 {
+ return &ValidationError{Name: "promo_code", err: errors.New(`ent: missing required edge "PromoCodeUsage.promo_code"`)}
+ }
+ if len(_c.mutation.UserIDs()) == 0 {
+ return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "PromoCodeUsage.user"`)}
+ }
+ return nil
+}
+
+func (_c *PromoCodeUsageCreate) sqlSave(ctx context.Context) (*PromoCodeUsage, error) {
+ if err := _c.check(); err != nil {
+ return nil, err
+ }
+ _node, _spec := _c.createSpec()
+ if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
+ if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return nil, err
+ }
+ id := _spec.ID.Value.(int64)
+ _node.ID = int64(id)
+ _c.mutation.id = &_node.ID
+ _c.mutation.done = true
+ return _node, nil
+}
+
+func (_c *PromoCodeUsageCreate) createSpec() (*PromoCodeUsage, *sqlgraph.CreateSpec) {
+ var (
+ _node = &PromoCodeUsage{config: _c.config}
+ _spec = sqlgraph.NewCreateSpec(promocodeusage.Table, sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64))
+ )
+ _spec.OnConflict = _c.conflict
+ if value, ok := _c.mutation.BonusAmount(); ok {
+ _spec.SetField(promocodeusage.FieldBonusAmount, field.TypeFloat64, value)
+ _node.BonusAmount = value
+ }
+ if value, ok := _c.mutation.UsedAt(); ok {
+ _spec.SetField(promocodeusage.FieldUsedAt, field.TypeTime, value)
+ _node.UsedAt = value
+ }
+ if nodes := _c.mutation.PromoCodeIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.PromoCodeTable,
+ Columns: []string{promocodeusage.PromoCodeColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _node.PromoCodeID = nodes[0]
+ _spec.Edges = append(_spec.Edges, edge)
+ }
+ if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.UserTable,
+ Columns: []string{promocodeusage.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _node.UserID = nodes[0]
+ _spec.Edges = append(_spec.Edges, edge)
+ }
+ return _node, _spec
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.PromoCodeUsage.Create().
+// SetPromoCodeID(v).
+// OnConflict(
+// // Update the row with the new values
+// // the was proposed for insertion.
+// sql.ResolveWithNewValues(),
+// ).
+// // Override some of the fields with custom
+// // update values.
+// Update(func(u *ent.PromoCodeUsageUpsert) {
+// SetPromoCodeID(v+v).
+// }).
+// Exec(ctx)
+func (_c *PromoCodeUsageCreate) OnConflict(opts ...sql.ConflictOption) *PromoCodeUsageUpsertOne {
+ _c.conflict = opts
+ return &PromoCodeUsageUpsertOne{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PromoCodeUsage.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PromoCodeUsageCreate) OnConflictColumns(columns ...string) *PromoCodeUsageUpsertOne {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PromoCodeUsageUpsertOne{
+ create: _c,
+ }
+}
+
+type (
+ // PromoCodeUsageUpsertOne is the builder for "upsert"-ing
+ // one PromoCodeUsage node.
+ PromoCodeUsageUpsertOne struct {
+ create *PromoCodeUsageCreate
+ }
+
+ // PromoCodeUsageUpsert is the "OnConflict" setter.
+ PromoCodeUsageUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (u *PromoCodeUsageUpsert) SetPromoCodeID(v int64) *PromoCodeUsageUpsert {
+ u.Set(promocodeusage.FieldPromoCodeID, v)
+ return u
+}
+
+// UpdatePromoCodeID sets the "promo_code_id" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsert) UpdatePromoCodeID() *PromoCodeUsageUpsert {
+ u.SetExcluded(promocodeusage.FieldPromoCodeID)
+ return u
+}
+
+// SetUserID sets the "user_id" field.
+func (u *PromoCodeUsageUpsert) SetUserID(v int64) *PromoCodeUsageUpsert {
+ u.Set(promocodeusage.FieldUserID, v)
+ return u
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsert) UpdateUserID() *PromoCodeUsageUpsert {
+ u.SetExcluded(promocodeusage.FieldUserID)
+ return u
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (u *PromoCodeUsageUpsert) SetBonusAmount(v float64) *PromoCodeUsageUpsert {
+ u.Set(promocodeusage.FieldBonusAmount, v)
+ return u
+}
+
+// UpdateBonusAmount sets the "bonus_amount" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsert) UpdateBonusAmount() *PromoCodeUsageUpsert {
+ u.SetExcluded(promocodeusage.FieldBonusAmount)
+ return u
+}
+
+// AddBonusAmount adds v to the "bonus_amount" field.
+func (u *PromoCodeUsageUpsert) AddBonusAmount(v float64) *PromoCodeUsageUpsert {
+ u.Add(promocodeusage.FieldBonusAmount, v)
+ return u
+}
+
+// SetUsedAt sets the "used_at" field.
+func (u *PromoCodeUsageUpsert) SetUsedAt(v time.Time) *PromoCodeUsageUpsert {
+ u.Set(promocodeusage.FieldUsedAt, v)
+ return u
+}
+
+// UpdateUsedAt sets the "used_at" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsert) UpdateUsedAt() *PromoCodeUsageUpsert {
+ u.SetExcluded(promocodeusage.FieldUsedAt)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were set on create.
+// Using this option is equivalent to using:
+//
+// client.PromoCodeUsage.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PromoCodeUsageUpsertOne) UpdateNewValues() *PromoCodeUsageUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PromoCodeUsage.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PromoCodeUsageUpsertOne) Ignore() *PromoCodeUsageUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+ return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *PromoCodeUsageUpsertOne) DoNothing() *PromoCodeUsageUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PromoCodeUsageCreate.OnConflict
+// documentation for more info.
+func (u *PromoCodeUsageUpsertOne) Update(set func(*PromoCodeUsageUpsert)) *PromoCodeUsageUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PromoCodeUsageUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (u *PromoCodeUsageUpsertOne) SetPromoCodeID(v int64) *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetPromoCodeID(v)
+ })
+}
+
+// UpdatePromoCodeID sets the "promo_code_id" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertOne) UpdatePromoCodeID() *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdatePromoCodeID()
+ })
+}
+
+// SetUserID sets the "user_id" field.
+func (u *PromoCodeUsageUpsertOne) SetUserID(v int64) *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetUserID(v)
+ })
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertOne) UpdateUserID() *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdateUserID()
+ })
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (u *PromoCodeUsageUpsertOne) SetBonusAmount(v float64) *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetBonusAmount(v)
+ })
+}
+
+// AddBonusAmount adds v to the "bonus_amount" field.
+func (u *PromoCodeUsageUpsertOne) AddBonusAmount(v float64) *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.AddBonusAmount(v)
+ })
+}
+
+// UpdateBonusAmount sets the "bonus_amount" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertOne) UpdateBonusAmount() *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdateBonusAmount()
+ })
+}
+
+// SetUsedAt sets the "used_at" field.
+func (u *PromoCodeUsageUpsertOne) SetUsedAt(v time.Time) *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetUsedAt(v)
+ })
+}
+
+// UpdateUsedAt sets the "used_at" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertOne) UpdateUsedAt() *PromoCodeUsageUpsertOne {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdateUsedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PromoCodeUsageUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PromoCodeUsageCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PromoCodeUsageUpsertOne) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// Exec executes the UPSERT query and returns the inserted/updated ID.
+func (u *PromoCodeUsageUpsertOne) ID(ctx context.Context) (id int64, err error) {
+ node, err := u.create.Save(ctx)
+ if err != nil {
+ return id, err
+ }
+ return node.ID, nil
+}
+
+// IDX is like ID, but panics if an error occurs.
+func (u *PromoCodeUsageUpsertOne) IDX(ctx context.Context) int64 {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// PromoCodeUsageCreateBulk is the builder for creating many PromoCodeUsage entities in bulk.
+type PromoCodeUsageCreateBulk struct {
+ config
+ err error
+ builders []*PromoCodeUsageCreate
+ conflict []sql.ConflictOption
+}
+
+// Save creates the PromoCodeUsage entities in the database.
+func (_c *PromoCodeUsageCreateBulk) Save(ctx context.Context) ([]*PromoCodeUsage, error) {
+ if _c.err != nil {
+ return nil, _c.err
+ }
+ specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
+ nodes := make([]*PromoCodeUsage, len(_c.builders))
+ mutators := make([]Mutator, len(_c.builders))
+ for i := range _c.builders {
+ func(i int, root context.Context) {
+ builder := _c.builders[i]
+ builder.defaults()
+ var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
+ mutation, ok := m.(*PromoCodeUsageMutation)
+ if !ok {
+ return nil, fmt.Errorf("unexpected mutation type %T", m)
+ }
+ if err := builder.check(); err != nil {
+ return nil, err
+ }
+ builder.mutation = mutation
+ var err error
+ nodes[i], specs[i] = builder.createSpec()
+ if i < len(mutators)-1 {
+ _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
+ } else {
+ spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
+ spec.OnConflict = _c.conflict
+ // Invoke the actual operation on the latest mutation in the chain.
+ if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
+ if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+ mutation.id = &nodes[i].ID
+ if specs[i].ID.Value != nil {
+ id := specs[i].ID.Value.(int64)
+ nodes[i].ID = int64(id)
+ }
+ mutation.done = true
+ return nodes[i], nil
+ })
+ for i := len(builder.hooks) - 1; i >= 0; i-- {
+ mut = builder.hooks[i](mut)
+ }
+ mutators[i] = mut
+ }(i, ctx)
+ }
+ if len(mutators) > 0 {
+ if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
+ return nil, err
+ }
+ }
+ return nodes, nil
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_c *PromoCodeUsageCreateBulk) SaveX(ctx context.Context) []*PromoCodeUsage {
+ v, err := _c.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
+
+// Exec executes the query.
+func (_c *PromoCodeUsageCreateBulk) Exec(ctx context.Context) error {
+ _, err := _c.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_c *PromoCodeUsageCreateBulk) ExecX(ctx context.Context) {
+ if err := _c.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.PromoCodeUsage.CreateBulk(builders...).
+// OnConflict(
+// // Update the row with the new values
+// // the was proposed for insertion.
+// sql.ResolveWithNewValues(),
+// ).
+// // Override some of the fields with custom
+// // update values.
+// Update(func(u *ent.PromoCodeUsageUpsert) {
+// SetPromoCodeID(v+v).
+// }).
+// Exec(ctx)
+func (_c *PromoCodeUsageCreateBulk) OnConflict(opts ...sql.ConflictOption) *PromoCodeUsageUpsertBulk {
+ _c.conflict = opts
+ return &PromoCodeUsageUpsertBulk{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.PromoCodeUsage.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *PromoCodeUsageCreateBulk) OnConflictColumns(columns ...string) *PromoCodeUsageUpsertBulk {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &PromoCodeUsageUpsertBulk{
+ create: _c,
+ }
+}
+
+// PromoCodeUsageUpsertBulk is the builder for "upsert"-ing
+// a bulk of PromoCodeUsage nodes.
+type PromoCodeUsageUpsertBulk struct {
+ create *PromoCodeUsageCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.PromoCodeUsage.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *PromoCodeUsageUpsertBulk) UpdateNewValues() *PromoCodeUsageUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.PromoCodeUsage.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *PromoCodeUsageUpsertBulk) Ignore() *PromoCodeUsageUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+ return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *PromoCodeUsageUpsertBulk) DoNothing() *PromoCodeUsageUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the PromoCodeUsageCreateBulk.OnConflict
+// documentation for more info.
+func (u *PromoCodeUsageUpsertBulk) Update(set func(*PromoCodeUsageUpsert)) *PromoCodeUsageUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&PromoCodeUsageUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (u *PromoCodeUsageUpsertBulk) SetPromoCodeID(v int64) *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetPromoCodeID(v)
+ })
+}
+
+// UpdatePromoCodeID sets the "promo_code_id" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertBulk) UpdatePromoCodeID() *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdatePromoCodeID()
+ })
+}
+
+// SetUserID sets the "user_id" field.
+func (u *PromoCodeUsageUpsertBulk) SetUserID(v int64) *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetUserID(v)
+ })
+}
+
+// UpdateUserID sets the "user_id" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertBulk) UpdateUserID() *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdateUserID()
+ })
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (u *PromoCodeUsageUpsertBulk) SetBonusAmount(v float64) *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetBonusAmount(v)
+ })
+}
+
+// AddBonusAmount adds v to the "bonus_amount" field.
+func (u *PromoCodeUsageUpsertBulk) AddBonusAmount(v float64) *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.AddBonusAmount(v)
+ })
+}
+
+// UpdateBonusAmount sets the "bonus_amount" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertBulk) UpdateBonusAmount() *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdateBonusAmount()
+ })
+}
+
+// SetUsedAt sets the "used_at" field.
+func (u *PromoCodeUsageUpsertBulk) SetUsedAt(v time.Time) *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.SetUsedAt(v)
+ })
+}
+
+// UpdateUsedAt sets the "used_at" field to the value that was provided on create.
+func (u *PromoCodeUsageUpsertBulk) UpdateUsedAt() *PromoCodeUsageUpsertBulk {
+ return u.Update(func(s *PromoCodeUsageUpsert) {
+ s.UpdateUsedAt()
+ })
+}
+
+// Exec executes the query.
+func (u *PromoCodeUsageUpsertBulk) Exec(ctx context.Context) error {
+ if u.create.err != nil {
+ return u.create.err
+ }
+ for i, b := range u.create.builders {
+ if len(b.conflict) != 0 {
+ return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the PromoCodeUsageCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for PromoCodeUsageCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *PromoCodeUsageUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/promocodeusage_delete.go b/backend/ent/promocodeusage_delete.go
new file mode 100644
index 00000000..bd3fa5e1
--- /dev/null
+++ b/backend/ent/promocodeusage_delete.go
@@ -0,0 +1,88 @@
+// 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/promocodeusage"
+)
+
+// PromoCodeUsageDelete is the builder for deleting a PromoCodeUsage entity.
+type PromoCodeUsageDelete struct {
+ config
+ hooks []Hook
+ mutation *PromoCodeUsageMutation
+}
+
+// Where appends a list predicates to the PromoCodeUsageDelete builder.
+func (_d *PromoCodeUsageDelete) Where(ps ...predicate.PromoCodeUsage) *PromoCodeUsageDelete {
+ _d.mutation.Where(ps...)
+ return _d
+}
+
+// Exec executes the deletion query and returns how many vertices were deleted.
+func (_d *PromoCodeUsageDelete) 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 *PromoCodeUsageDelete) ExecX(ctx context.Context) int {
+ n, err := _d.Exec(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func (_d *PromoCodeUsageDelete) sqlExec(ctx context.Context) (int, error) {
+ _spec := sqlgraph.NewDeleteSpec(promocodeusage.Table, sqlgraph.NewFieldSpec(promocodeusage.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
+}
+
+// PromoCodeUsageDeleteOne is the builder for deleting a single PromoCodeUsage entity.
+type PromoCodeUsageDeleteOne struct {
+ _d *PromoCodeUsageDelete
+}
+
+// Where appends a list predicates to the PromoCodeUsageDelete builder.
+func (_d *PromoCodeUsageDeleteOne) Where(ps ...predicate.PromoCodeUsage) *PromoCodeUsageDeleteOne {
+ _d._d.mutation.Where(ps...)
+ return _d
+}
+
+// Exec executes the deletion query.
+func (_d *PromoCodeUsageDeleteOne) Exec(ctx context.Context) error {
+ n, err := _d._d.Exec(ctx)
+ switch {
+ case err != nil:
+ return err
+ case n == 0:
+ return &NotFoundError{promocodeusage.Label}
+ default:
+ return nil
+ }
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_d *PromoCodeUsageDeleteOne) ExecX(ctx context.Context) {
+ if err := _d.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/backend/ent/promocodeusage_query.go b/backend/ent/promocodeusage_query.go
new file mode 100644
index 00000000..95b02a16
--- /dev/null
+++ b/backend/ent/promocodeusage_query.go
@@ -0,0 +1,718 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+ "context"
+ "fmt"
+ "math"
+
+ "entgo.io/ent"
+ "entgo.io/ent/dialect"
+ "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/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+ "github.com/Wei-Shaw/sub2api/ent/user"
+)
+
+// PromoCodeUsageQuery is the builder for querying PromoCodeUsage entities.
+type PromoCodeUsageQuery struct {
+ config
+ ctx *QueryContext
+ order []promocodeusage.OrderOption
+ inters []Interceptor
+ predicates []predicate.PromoCodeUsage
+ withPromoCode *PromoCodeQuery
+ withUser *UserQuery
+ modifiers []func(*sql.Selector)
+ // intermediate query (i.e. traversal path).
+ sql *sql.Selector
+ path func(context.Context) (*sql.Selector, error)
+}
+
+// Where adds a new predicate for the PromoCodeUsageQuery builder.
+func (_q *PromoCodeUsageQuery) Where(ps ...predicate.PromoCodeUsage) *PromoCodeUsageQuery {
+ _q.predicates = append(_q.predicates, ps...)
+ return _q
+}
+
+// Limit the number of records to be returned by this query.
+func (_q *PromoCodeUsageQuery) Limit(limit int) *PromoCodeUsageQuery {
+ _q.ctx.Limit = &limit
+ return _q
+}
+
+// Offset to start from.
+func (_q *PromoCodeUsageQuery) Offset(offset int) *PromoCodeUsageQuery {
+ _q.ctx.Offset = &offset
+ return _q
+}
+
+// Unique configures the query builder to filter duplicate records on query.
+// By default, unique is set to true, and can be disabled using this method.
+func (_q *PromoCodeUsageQuery) Unique(unique bool) *PromoCodeUsageQuery {
+ _q.ctx.Unique = &unique
+ return _q
+}
+
+// Order specifies how the records should be ordered.
+func (_q *PromoCodeUsageQuery) Order(o ...promocodeusage.OrderOption) *PromoCodeUsageQuery {
+ _q.order = append(_q.order, o...)
+ return _q
+}
+
+// QueryPromoCode chains the current query on the "promo_code" edge.
+func (_q *PromoCodeUsageQuery) QueryPromoCode() *PromoCodeQuery {
+ query := (&PromoCodeClient{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(promocodeusage.Table, promocodeusage.FieldID, selector),
+ sqlgraph.To(promocode.Table, promocode.FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, promocodeusage.PromoCodeTable, promocodeusage.PromoCodeColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
+// QueryUser chains the current query on the "user" edge.
+func (_q *PromoCodeUsageQuery) QueryUser() *UserQuery {
+ query := (&UserClient{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(promocodeusage.Table, promocodeusage.FieldID, selector),
+ sqlgraph.To(user.Table, user.FieldID),
+ sqlgraph.Edge(sqlgraph.M2O, true, promocodeusage.UserTable, promocodeusage.UserColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
+// First returns the first PromoCodeUsage entity from the query.
+// Returns a *NotFoundError when no PromoCodeUsage was found.
+func (_q *PromoCodeUsageQuery) First(ctx context.Context) (*PromoCodeUsage, error) {
+ nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
+ if err != nil {
+ return nil, err
+ }
+ if len(nodes) == 0 {
+ return nil, &NotFoundError{promocodeusage.Label}
+ }
+ return nodes[0], nil
+}
+
+// FirstX is like First, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) FirstX(ctx context.Context) *PromoCodeUsage {
+ node, err := _q.First(ctx)
+ if err != nil && !IsNotFound(err) {
+ panic(err)
+ }
+ return node
+}
+
+// FirstID returns the first PromoCodeUsage ID from the query.
+// Returns a *NotFoundError when no PromoCodeUsage ID was found.
+func (_q *PromoCodeUsageQuery) FirstID(ctx context.Context) (id int64, err error) {
+ var ids []int64
+ if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
+ return
+ }
+ if len(ids) == 0 {
+ err = &NotFoundError{promocodeusage.Label}
+ return
+ }
+ return ids[0], nil
+}
+
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) FirstIDX(ctx context.Context) int64 {
+ id, err := _q.FirstID(ctx)
+ if err != nil && !IsNotFound(err) {
+ panic(err)
+ }
+ return id
+}
+
+// Only returns a single PromoCodeUsage entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when more than one PromoCodeUsage entity is found.
+// Returns a *NotFoundError when no PromoCodeUsage entities are found.
+func (_q *PromoCodeUsageQuery) Only(ctx context.Context) (*PromoCodeUsage, error) {
+ nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
+ if err != nil {
+ return nil, err
+ }
+ switch len(nodes) {
+ case 1:
+ return nodes[0], nil
+ case 0:
+ return nil, &NotFoundError{promocodeusage.Label}
+ default:
+ return nil, &NotSingularError{promocodeusage.Label}
+ }
+}
+
+// OnlyX is like Only, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) OnlyX(ctx context.Context) *PromoCodeUsage {
+ node, err := _q.Only(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// OnlyID is like Only, but returns the only PromoCodeUsage ID in the query.
+// Returns a *NotSingularError when more than one PromoCodeUsage ID is found.
+// Returns a *NotFoundError when no entities are found.
+func (_q *PromoCodeUsageQuery) OnlyID(ctx context.Context) (id int64, err error) {
+ var ids []int64
+ if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
+ return
+ }
+ switch len(ids) {
+ case 1:
+ id = ids[0]
+ case 0:
+ err = &NotFoundError{promocodeusage.Label}
+ default:
+ err = &NotSingularError{promocodeusage.Label}
+ }
+ return
+}
+
+// OnlyIDX is like OnlyID, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) OnlyIDX(ctx context.Context) int64 {
+ id, err := _q.OnlyID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+// All executes the query and returns a list of PromoCodeUsages.
+func (_q *PromoCodeUsageQuery) All(ctx context.Context) ([]*PromoCodeUsage, error) {
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
+ if err := _q.prepareQuery(ctx); err != nil {
+ return nil, err
+ }
+ qr := querierAll[[]*PromoCodeUsage, *PromoCodeUsageQuery]()
+ return withInterceptors[[]*PromoCodeUsage](ctx, _q, qr, _q.inters)
+}
+
+// AllX is like All, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) AllX(ctx context.Context) []*PromoCodeUsage {
+ nodes, err := _q.All(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return nodes
+}
+
+// IDs executes the query and returns a list of PromoCodeUsage IDs.
+func (_q *PromoCodeUsageQuery) IDs(ctx context.Context) (ids []int64, err error) {
+ if _q.ctx.Unique == nil && _q.path != nil {
+ _q.Unique(true)
+ }
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
+ if err = _q.Select(promocodeusage.FieldID).Scan(ctx, &ids); err != nil {
+ return nil, err
+ }
+ return ids, nil
+}
+
+// IDsX is like IDs, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) IDsX(ctx context.Context) []int64 {
+ ids, err := _q.IDs(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return ids
+}
+
+// Count returns the count of the given query.
+func (_q *PromoCodeUsageQuery) Count(ctx context.Context) (int, error) {
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
+ if err := _q.prepareQuery(ctx); err != nil {
+ return 0, err
+ }
+ return withInterceptors[int](ctx, _q, querierCount[*PromoCodeUsageQuery](), _q.inters)
+}
+
+// CountX is like Count, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) CountX(ctx context.Context) int {
+ count, err := _q.Count(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return count
+}
+
+// Exist returns true if the query has elements in the graph.
+func (_q *PromoCodeUsageQuery) Exist(ctx context.Context) (bool, error) {
+ ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
+ switch _, err := _q.FirstID(ctx); {
+ case IsNotFound(err):
+ return false, nil
+ case err != nil:
+ return false, fmt.Errorf("ent: check existence: %w", err)
+ default:
+ return true, nil
+ }
+}
+
+// ExistX is like Exist, but panics if an error occurs.
+func (_q *PromoCodeUsageQuery) ExistX(ctx context.Context) bool {
+ exist, err := _q.Exist(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return exist
+}
+
+// Clone returns a duplicate of the PromoCodeUsageQuery builder, including all associated steps. It can be
+// used to prepare common query builders and use them differently after the clone is made.
+func (_q *PromoCodeUsageQuery) Clone() *PromoCodeUsageQuery {
+ if _q == nil {
+ return nil
+ }
+ return &PromoCodeUsageQuery{
+ config: _q.config,
+ ctx: _q.ctx.Clone(),
+ order: append([]promocodeusage.OrderOption{}, _q.order...),
+ inters: append([]Interceptor{}, _q.inters...),
+ predicates: append([]predicate.PromoCodeUsage{}, _q.predicates...),
+ withPromoCode: _q.withPromoCode.Clone(),
+ withUser: _q.withUser.Clone(),
+ // clone intermediate query.
+ sql: _q.sql.Clone(),
+ path: _q.path,
+ }
+}
+
+// WithPromoCode tells the query-builder to eager-load the nodes that are connected to
+// the "promo_code" edge. The optional arguments are used to configure the query builder of the edge.
+func (_q *PromoCodeUsageQuery) WithPromoCode(opts ...func(*PromoCodeQuery)) *PromoCodeUsageQuery {
+ query := (&PromoCodeClient{config: _q.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ _q.withPromoCode = query
+ return _q
+}
+
+// WithUser tells the query-builder to eager-load the nodes that are connected to
+// the "user" edge. The optional arguments are used to configure the query builder of the edge.
+func (_q *PromoCodeUsageQuery) WithUser(opts ...func(*UserQuery)) *PromoCodeUsageQuery {
+ query := (&UserClient{config: _q.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ _q.withUser = 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.
+//
+// Example:
+//
+// var v []struct {
+// PromoCodeID int64 `json:"promo_code_id,omitempty"`
+// Count int `json:"count,omitempty"`
+// }
+//
+// client.PromoCodeUsage.Query().
+// GroupBy(promocodeusage.FieldPromoCodeID).
+// Aggregate(ent.Count()).
+// Scan(ctx, &v)
+func (_q *PromoCodeUsageQuery) GroupBy(field string, fields ...string) *PromoCodeUsageGroupBy {
+ _q.ctx.Fields = append([]string{field}, fields...)
+ grbuild := &PromoCodeUsageGroupBy{build: _q}
+ grbuild.flds = &_q.ctx.Fields
+ grbuild.label = promocodeusage.Label
+ grbuild.scan = grbuild.Scan
+ return grbuild
+}
+
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
+//
+// Example:
+//
+// var v []struct {
+// PromoCodeID int64 `json:"promo_code_id,omitempty"`
+// }
+//
+// client.PromoCodeUsage.Query().
+// Select(promocodeusage.FieldPromoCodeID).
+// Scan(ctx, &v)
+func (_q *PromoCodeUsageQuery) Select(fields ...string) *PromoCodeUsageSelect {
+ _q.ctx.Fields = append(_q.ctx.Fields, fields...)
+ sbuild := &PromoCodeUsageSelect{PromoCodeUsageQuery: _q}
+ sbuild.label = promocodeusage.Label
+ sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
+ return sbuild
+}
+
+// Aggregate returns a PromoCodeUsageSelect configured with the given aggregations.
+func (_q *PromoCodeUsageQuery) Aggregate(fns ...AggregateFunc) *PromoCodeUsageSelect {
+ return _q.Select().Aggregate(fns...)
+}
+
+func (_q *PromoCodeUsageQuery) prepareQuery(ctx context.Context) error {
+ for _, inter := range _q.inters {
+ if inter == nil {
+ return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
+ }
+ if trv, ok := inter.(Traverser); ok {
+ if err := trv.Traverse(ctx, _q); err != nil {
+ return err
+ }
+ }
+ }
+ for _, f := range _q.ctx.Fields {
+ if !promocodeusage.ValidColumn(f) {
+ return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ }
+ if _q.path != nil {
+ prev, err := _q.path(ctx)
+ if err != nil {
+ return err
+ }
+ _q.sql = prev
+ }
+ return nil
+}
+
+func (_q *PromoCodeUsageQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PromoCodeUsage, error) {
+ var (
+ nodes = []*PromoCodeUsage{}
+ _spec = _q.querySpec()
+ loadedTypes = [2]bool{
+ _q.withPromoCode != nil,
+ _q.withUser != nil,
+ }
+ )
+ _spec.ScanValues = func(columns []string) ([]any, error) {
+ return (*PromoCodeUsage).scanValues(nil, columns)
+ }
+ _spec.Assign = func(columns []string, values []any) error {
+ node := &PromoCodeUsage{config: _q.config}
+ nodes = append(nodes, node)
+ node.Edges.loadedTypes = loadedTypes
+ return node.assignValues(columns, values)
+ }
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
+ for i := range hooks {
+ hooks[i](ctx, _spec)
+ }
+ if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
+ return nil, err
+ }
+ if len(nodes) == 0 {
+ return nodes, nil
+ }
+ if query := _q.withPromoCode; query != nil {
+ if err := _q.loadPromoCode(ctx, query, nodes, nil,
+ func(n *PromoCodeUsage, e *PromoCode) { n.Edges.PromoCode = e }); err != nil {
+ return nil, err
+ }
+ }
+ if query := _q.withUser; query != nil {
+ if err := _q.loadUser(ctx, query, nodes, nil,
+ func(n *PromoCodeUsage, e *User) { n.Edges.User = e }); err != nil {
+ return nil, err
+ }
+ }
+ return nodes, nil
+}
+
+func (_q *PromoCodeUsageQuery) loadPromoCode(ctx context.Context, query *PromoCodeQuery, nodes []*PromoCodeUsage, init func(*PromoCodeUsage), assign func(*PromoCodeUsage, *PromoCode)) error {
+ ids := make([]int64, 0, len(nodes))
+ nodeids := make(map[int64][]*PromoCodeUsage)
+ for i := range nodes {
+ fk := nodes[i].PromoCodeID
+ if _, ok := nodeids[fk]; !ok {
+ ids = append(ids, fk)
+ }
+ nodeids[fk] = append(nodeids[fk], nodes[i])
+ }
+ if len(ids) == 0 {
+ return nil
+ }
+ query.Where(promocode.IDIn(ids...))
+ neighbors, err := query.All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, n := range neighbors {
+ nodes, ok := nodeids[n.ID]
+ if !ok {
+ return fmt.Errorf(`unexpected foreign-key "promo_code_id" returned %v`, n.ID)
+ }
+ for i := range nodes {
+ assign(nodes[i], n)
+ }
+ }
+ return nil
+}
+func (_q *PromoCodeUsageQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*PromoCodeUsage, init func(*PromoCodeUsage), assign func(*PromoCodeUsage, *User)) error {
+ ids := make([]int64, 0, len(nodes))
+ nodeids := make(map[int64][]*PromoCodeUsage)
+ for i := range nodes {
+ fk := nodes[i].UserID
+ if _, ok := nodeids[fk]; !ok {
+ ids = append(ids, fk)
+ }
+ nodeids[fk] = append(nodeids[fk], nodes[i])
+ }
+ if len(ids) == 0 {
+ return nil
+ }
+ query.Where(user.IDIn(ids...))
+ neighbors, err := query.All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, n := range neighbors {
+ nodes, ok := nodeids[n.ID]
+ if !ok {
+ return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID)
+ }
+ for i := range nodes {
+ assign(nodes[i], n)
+ }
+ }
+ return nil
+}
+
+func (_q *PromoCodeUsageQuery) sqlCount(ctx context.Context) (int, error) {
+ _spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
+ _spec.Node.Columns = _q.ctx.Fields
+ if len(_q.ctx.Fields) > 0 {
+ _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
+ }
+ return sqlgraph.CountNodes(ctx, _q.driver, _spec)
+}
+
+func (_q *PromoCodeUsageQuery) querySpec() *sqlgraph.QuerySpec {
+ _spec := sqlgraph.NewQuerySpec(promocodeusage.Table, promocodeusage.Columns, sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64))
+ _spec.From = _q.sql
+ if unique := _q.ctx.Unique; unique != nil {
+ _spec.Unique = *unique
+ } else if _q.path != nil {
+ _spec.Unique = true
+ }
+ if fields := _q.ctx.Fields; len(fields) > 0 {
+ _spec.Node.Columns = make([]string, 0, len(fields))
+ _spec.Node.Columns = append(_spec.Node.Columns, promocodeusage.FieldID)
+ for i := range fields {
+ if fields[i] != promocodeusage.FieldID {
+ _spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+ }
+ }
+ if _q.withPromoCode != nil {
+ _spec.Node.AddColumnOnce(promocodeusage.FieldPromoCodeID)
+ }
+ if _q.withUser != nil {
+ _spec.Node.AddColumnOnce(promocodeusage.FieldUserID)
+ }
+ }
+ if ps := _q.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if limit := _q.ctx.Limit; limit != nil {
+ _spec.Limit = *limit
+ }
+ if offset := _q.ctx.Offset; offset != nil {
+ _spec.Offset = *offset
+ }
+ if ps := _q.order; len(ps) > 0 {
+ _spec.Order = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ return _spec
+}
+
+func (_q *PromoCodeUsageQuery) sqlQuery(ctx context.Context) *sql.Selector {
+ builder := sql.Dialect(_q.driver.Dialect())
+ t1 := builder.Table(promocodeusage.Table)
+ columns := _q.ctx.Fields
+ if len(columns) == 0 {
+ columns = promocodeusage.Columns
+ }
+ selector := builder.Select(t1.Columns(columns...)...).From(t1)
+ if _q.sql != nil {
+ selector = _q.sql
+ selector.Select(selector.Columns(columns...)...)
+ }
+ if _q.ctx.Unique != nil && *_q.ctx.Unique {
+ selector.Distinct()
+ }
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
+ for _, p := range _q.predicates {
+ p(selector)
+ }
+ for _, p := range _q.order {
+ p(selector)
+ }
+ if offset := _q.ctx.Offset; offset != nil {
+ // limit is mandatory for offset clause. We start
+ // with default value, and override it below if needed.
+ selector.Offset(*offset).Limit(math.MaxInt32)
+ }
+ if limit := _q.ctx.Limit; limit != nil {
+ selector.Limit(*limit)
+ }
+ return selector
+}
+
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *PromoCodeUsageQuery) ForUpdate(opts ...sql.LockOption) *PromoCodeUsageQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *PromoCodeUsageQuery) ForShare(opts ...sql.LockOption) *PromoCodeUsageQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
+// PromoCodeUsageGroupBy is the group-by builder for PromoCodeUsage entities.
+type PromoCodeUsageGroupBy struct {
+ selector
+ build *PromoCodeUsageQuery
+}
+
+// Aggregate adds the given aggregation functions to the group-by query.
+func (_g *PromoCodeUsageGroupBy) Aggregate(fns ...AggregateFunc) *PromoCodeUsageGroupBy {
+ _g.fns = append(_g.fns, fns...)
+ return _g
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (_g *PromoCodeUsageGroupBy) Scan(ctx context.Context, v any) error {
+ ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
+ if err := _g.build.prepareQuery(ctx); err != nil {
+ return err
+ }
+ return scanWithInterceptors[*PromoCodeUsageQuery, *PromoCodeUsageGroupBy](ctx, _g.build, _g, _g.build.inters, v)
+}
+
+func (_g *PromoCodeUsageGroupBy) sqlScan(ctx context.Context, root *PromoCodeUsageQuery, v any) error {
+ selector := root.sqlQuery(ctx).Select()
+ aggregation := make([]string, 0, len(_g.fns))
+ for _, fn := range _g.fns {
+ aggregation = append(aggregation, fn(selector))
+ }
+ if len(selector.SelectedColumns()) == 0 {
+ columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
+ for _, f := range *_g.flds {
+ columns = append(columns, selector.C(f))
+ }
+ columns = append(columns, aggregation...)
+ selector.Select(columns...)
+ }
+ selector.GroupBy(selector.Columns(*_g.flds...)...)
+ if err := selector.Err(); err != nil {
+ return err
+ }
+ rows := &sql.Rows{}
+ query, args := selector.Query()
+ if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
+ return err
+ }
+ defer rows.Close()
+ return sql.ScanSlice(rows, v)
+}
+
+// PromoCodeUsageSelect is the builder for selecting fields of PromoCodeUsage entities.
+type PromoCodeUsageSelect struct {
+ *PromoCodeUsageQuery
+ selector
+}
+
+// Aggregate adds the given aggregation functions to the selector query.
+func (_s *PromoCodeUsageSelect) Aggregate(fns ...AggregateFunc) *PromoCodeUsageSelect {
+ _s.fns = append(_s.fns, fns...)
+ return _s
+}
+
+// Scan applies the selector query and scans the result into the given value.
+func (_s *PromoCodeUsageSelect) Scan(ctx context.Context, v any) error {
+ ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
+ if err := _s.prepareQuery(ctx); err != nil {
+ return err
+ }
+ return scanWithInterceptors[*PromoCodeUsageQuery, *PromoCodeUsageSelect](ctx, _s.PromoCodeUsageQuery, _s, _s.inters, v)
+}
+
+func (_s *PromoCodeUsageSelect) sqlScan(ctx context.Context, root *PromoCodeUsageQuery, v any) error {
+ selector := root.sqlQuery(ctx)
+ aggregation := make([]string, 0, len(_s.fns))
+ for _, fn := range _s.fns {
+ aggregation = append(aggregation, fn(selector))
+ }
+ switch n := len(*_s.selector.flds); {
+ case n == 0 && len(aggregation) > 0:
+ selector.Select(aggregation...)
+ case n != 0 && len(aggregation) > 0:
+ selector.AppendSelect(aggregation...)
+ }
+ rows := &sql.Rows{}
+ query, args := selector.Query()
+ if err := _s.driver.Query(ctx, query, args, rows); err != nil {
+ return err
+ }
+ defer rows.Close()
+ return sql.ScanSlice(rows, v)
+}
diff --git a/backend/ent/promocodeusage_update.go b/backend/ent/promocodeusage_update.go
new file mode 100644
index 00000000..d91a1f10
--- /dev/null
+++ b/backend/ent/promocodeusage_update.go
@@ -0,0 +1,510 @@
+// Code generated by ent, DO NOT EDIT.
+
+package ent
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "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/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+ "github.com/Wei-Shaw/sub2api/ent/user"
+)
+
+// PromoCodeUsageUpdate is the builder for updating PromoCodeUsage entities.
+type PromoCodeUsageUpdate struct {
+ config
+ hooks []Hook
+ mutation *PromoCodeUsageMutation
+}
+
+// Where appends a list predicates to the PromoCodeUsageUpdate builder.
+func (_u *PromoCodeUsageUpdate) Where(ps ...predicate.PromoCodeUsage) *PromoCodeUsageUpdate {
+ _u.mutation.Where(ps...)
+ return _u
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (_u *PromoCodeUsageUpdate) SetPromoCodeID(v int64) *PromoCodeUsageUpdate {
+ _u.mutation.SetPromoCodeID(v)
+ return _u
+}
+
+// SetNillablePromoCodeID sets the "promo_code_id" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdate) SetNillablePromoCodeID(v *int64) *PromoCodeUsageUpdate {
+ if v != nil {
+ _u.SetPromoCodeID(*v)
+ }
+ return _u
+}
+
+// SetUserID sets the "user_id" field.
+func (_u *PromoCodeUsageUpdate) SetUserID(v int64) *PromoCodeUsageUpdate {
+ _u.mutation.SetUserID(v)
+ return _u
+}
+
+// SetNillableUserID sets the "user_id" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdate) SetNillableUserID(v *int64) *PromoCodeUsageUpdate {
+ if v != nil {
+ _u.SetUserID(*v)
+ }
+ return _u
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (_u *PromoCodeUsageUpdate) SetBonusAmount(v float64) *PromoCodeUsageUpdate {
+ _u.mutation.ResetBonusAmount()
+ _u.mutation.SetBonusAmount(v)
+ return _u
+}
+
+// SetNillableBonusAmount sets the "bonus_amount" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdate) SetNillableBonusAmount(v *float64) *PromoCodeUsageUpdate {
+ if v != nil {
+ _u.SetBonusAmount(*v)
+ }
+ return _u
+}
+
+// AddBonusAmount adds value to the "bonus_amount" field.
+func (_u *PromoCodeUsageUpdate) AddBonusAmount(v float64) *PromoCodeUsageUpdate {
+ _u.mutation.AddBonusAmount(v)
+ return _u
+}
+
+// SetUsedAt sets the "used_at" field.
+func (_u *PromoCodeUsageUpdate) SetUsedAt(v time.Time) *PromoCodeUsageUpdate {
+ _u.mutation.SetUsedAt(v)
+ return _u
+}
+
+// SetNillableUsedAt sets the "used_at" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdate) SetNillableUsedAt(v *time.Time) *PromoCodeUsageUpdate {
+ if v != nil {
+ _u.SetUsedAt(*v)
+ }
+ return _u
+}
+
+// SetPromoCode sets the "promo_code" edge to the PromoCode entity.
+func (_u *PromoCodeUsageUpdate) SetPromoCode(v *PromoCode) *PromoCodeUsageUpdate {
+ return _u.SetPromoCodeID(v.ID)
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (_u *PromoCodeUsageUpdate) SetUser(v *User) *PromoCodeUsageUpdate {
+ return _u.SetUserID(v.ID)
+}
+
+// Mutation returns the PromoCodeUsageMutation object of the builder.
+func (_u *PromoCodeUsageUpdate) Mutation() *PromoCodeUsageMutation {
+ return _u.mutation
+}
+
+// ClearPromoCode clears the "promo_code" edge to the PromoCode entity.
+func (_u *PromoCodeUsageUpdate) ClearPromoCode() *PromoCodeUsageUpdate {
+ _u.mutation.ClearPromoCode()
+ return _u
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (_u *PromoCodeUsageUpdate) ClearUser() *PromoCodeUsageUpdate {
+ _u.mutation.ClearUser()
+ return _u
+}
+
+// Save executes the query and returns the number of nodes affected by the update operation.
+func (_u *PromoCodeUsageUpdate) Save(ctx context.Context) (int, error) {
+ return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_u *PromoCodeUsageUpdate) SaveX(ctx context.Context) int {
+ affected, err := _u.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return affected
+}
+
+// Exec executes the query.
+func (_u *PromoCodeUsageUpdate) Exec(ctx context.Context) error {
+ _, err := _u.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_u *PromoCodeUsageUpdate) ExecX(ctx context.Context) {
+ if err := _u.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_u *PromoCodeUsageUpdate) check() error {
+ if _u.mutation.PromoCodeCleared() && len(_u.mutation.PromoCodeIDs()) > 0 {
+ return errors.New(`ent: clearing a required unique edge "PromoCodeUsage.promo_code"`)
+ }
+ if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
+ return errors.New(`ent: clearing a required unique edge "PromoCodeUsage.user"`)
+ }
+ return nil
+}
+
+func (_u *PromoCodeUsageUpdate) sqlSave(ctx context.Context) (_node int, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(promocodeusage.Table, promocodeusage.Columns, sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64))
+ if ps := _u.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if value, ok := _u.mutation.BonusAmount(); ok {
+ _spec.SetField(promocodeusage.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedBonusAmount(); ok {
+ _spec.AddField(promocodeusage.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.UsedAt(); ok {
+ _spec.SetField(promocodeusage.FieldUsedAt, field.TypeTime, value)
+ }
+ if _u.mutation.PromoCodeCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.PromoCodeTable,
+ Columns: []string{promocodeusage.PromoCodeColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.PromoCodeIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.PromoCodeTable,
+ Columns: []string{promocodeusage.PromoCodeColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocode.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.UserCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.UserTable,
+ Columns: []string{promocodeusage.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.UserTable,
+ Columns: []string{promocodeusage.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.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{promocodeusage.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return 0, err
+ }
+ _u.mutation.done = true
+ return _node, nil
+}
+
+// PromoCodeUsageUpdateOne is the builder for updating a single PromoCodeUsage entity.
+type PromoCodeUsageUpdateOne struct {
+ config
+ fields []string
+ hooks []Hook
+ mutation *PromoCodeUsageMutation
+}
+
+// SetPromoCodeID sets the "promo_code_id" field.
+func (_u *PromoCodeUsageUpdateOne) SetPromoCodeID(v int64) *PromoCodeUsageUpdateOne {
+ _u.mutation.SetPromoCodeID(v)
+ return _u
+}
+
+// SetNillablePromoCodeID sets the "promo_code_id" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdateOne) SetNillablePromoCodeID(v *int64) *PromoCodeUsageUpdateOne {
+ if v != nil {
+ _u.SetPromoCodeID(*v)
+ }
+ return _u
+}
+
+// SetUserID sets the "user_id" field.
+func (_u *PromoCodeUsageUpdateOne) SetUserID(v int64) *PromoCodeUsageUpdateOne {
+ _u.mutation.SetUserID(v)
+ return _u
+}
+
+// SetNillableUserID sets the "user_id" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdateOne) SetNillableUserID(v *int64) *PromoCodeUsageUpdateOne {
+ if v != nil {
+ _u.SetUserID(*v)
+ }
+ return _u
+}
+
+// SetBonusAmount sets the "bonus_amount" field.
+func (_u *PromoCodeUsageUpdateOne) SetBonusAmount(v float64) *PromoCodeUsageUpdateOne {
+ _u.mutation.ResetBonusAmount()
+ _u.mutation.SetBonusAmount(v)
+ return _u
+}
+
+// SetNillableBonusAmount sets the "bonus_amount" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdateOne) SetNillableBonusAmount(v *float64) *PromoCodeUsageUpdateOne {
+ if v != nil {
+ _u.SetBonusAmount(*v)
+ }
+ return _u
+}
+
+// AddBonusAmount adds value to the "bonus_amount" field.
+func (_u *PromoCodeUsageUpdateOne) AddBonusAmount(v float64) *PromoCodeUsageUpdateOne {
+ _u.mutation.AddBonusAmount(v)
+ return _u
+}
+
+// SetUsedAt sets the "used_at" field.
+func (_u *PromoCodeUsageUpdateOne) SetUsedAt(v time.Time) *PromoCodeUsageUpdateOne {
+ _u.mutation.SetUsedAt(v)
+ return _u
+}
+
+// SetNillableUsedAt sets the "used_at" field if the given value is not nil.
+func (_u *PromoCodeUsageUpdateOne) SetNillableUsedAt(v *time.Time) *PromoCodeUsageUpdateOne {
+ if v != nil {
+ _u.SetUsedAt(*v)
+ }
+ return _u
+}
+
+// SetPromoCode sets the "promo_code" edge to the PromoCode entity.
+func (_u *PromoCodeUsageUpdateOne) SetPromoCode(v *PromoCode) *PromoCodeUsageUpdateOne {
+ return _u.SetPromoCodeID(v.ID)
+}
+
+// SetUser sets the "user" edge to the User entity.
+func (_u *PromoCodeUsageUpdateOne) SetUser(v *User) *PromoCodeUsageUpdateOne {
+ return _u.SetUserID(v.ID)
+}
+
+// Mutation returns the PromoCodeUsageMutation object of the builder.
+func (_u *PromoCodeUsageUpdateOne) Mutation() *PromoCodeUsageMutation {
+ return _u.mutation
+}
+
+// ClearPromoCode clears the "promo_code" edge to the PromoCode entity.
+func (_u *PromoCodeUsageUpdateOne) ClearPromoCode() *PromoCodeUsageUpdateOne {
+ _u.mutation.ClearPromoCode()
+ return _u
+}
+
+// ClearUser clears the "user" edge to the User entity.
+func (_u *PromoCodeUsageUpdateOne) ClearUser() *PromoCodeUsageUpdateOne {
+ _u.mutation.ClearUser()
+ return _u
+}
+
+// Where appends a list predicates to the PromoCodeUsageUpdate builder.
+func (_u *PromoCodeUsageUpdateOne) Where(ps ...predicate.PromoCodeUsage) *PromoCodeUsageUpdateOne {
+ _u.mutation.Where(ps...)
+ return _u
+}
+
+// Select allows selecting one or more fields (columns) of the returned entity.
+// The default is selecting all fields defined in the entity schema.
+func (_u *PromoCodeUsageUpdateOne) Select(field string, fields ...string) *PromoCodeUsageUpdateOne {
+ _u.fields = append([]string{field}, fields...)
+ return _u
+}
+
+// Save executes the query and returns the updated PromoCodeUsage entity.
+func (_u *PromoCodeUsageUpdateOne) Save(ctx context.Context) (*PromoCodeUsage, error) {
+ return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (_u *PromoCodeUsageUpdateOne) SaveX(ctx context.Context) *PromoCodeUsage {
+ node, err := _u.Save(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+// Exec executes the query on the entity.
+func (_u *PromoCodeUsageUpdateOne) Exec(ctx context.Context) error {
+ _, err := _u.Save(ctx)
+ return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (_u *PromoCodeUsageUpdateOne) ExecX(ctx context.Context) {
+ if err := _u.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// check runs all checks and user-defined validators on the builder.
+func (_u *PromoCodeUsageUpdateOne) check() error {
+ if _u.mutation.PromoCodeCleared() && len(_u.mutation.PromoCodeIDs()) > 0 {
+ return errors.New(`ent: clearing a required unique edge "PromoCodeUsage.promo_code"`)
+ }
+ if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 {
+ return errors.New(`ent: clearing a required unique edge "PromoCodeUsage.user"`)
+ }
+ return nil
+}
+
+func (_u *PromoCodeUsageUpdateOne) sqlSave(ctx context.Context) (_node *PromoCodeUsage, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
+ _spec := sqlgraph.NewUpdateSpec(promocodeusage.Table, promocodeusage.Columns, sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64))
+ id, ok := _u.mutation.ID()
+ if !ok {
+ return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PromoCodeUsage.id" for update`)}
+ }
+ _spec.Node.ID.Value = id
+ if fields := _u.fields; len(fields) > 0 {
+ _spec.Node.Columns = make([]string, 0, len(fields))
+ _spec.Node.Columns = append(_spec.Node.Columns, promocodeusage.FieldID)
+ for _, f := range fields {
+ if !promocodeusage.ValidColumn(f) {
+ return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+ }
+ if f != promocodeusage.FieldID {
+ _spec.Node.Columns = append(_spec.Node.Columns, f)
+ }
+ }
+ }
+ if ps := _u.mutation.predicates; len(ps) > 0 {
+ _spec.Predicate = func(selector *sql.Selector) {
+ for i := range ps {
+ ps[i](selector)
+ }
+ }
+ }
+ if value, ok := _u.mutation.BonusAmount(); ok {
+ _spec.SetField(promocodeusage.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.AddedBonusAmount(); ok {
+ _spec.AddField(promocodeusage.FieldBonusAmount, field.TypeFloat64, value)
+ }
+ if value, ok := _u.mutation.UsedAt(); ok {
+ _spec.SetField(promocodeusage.FieldUsedAt, field.TypeTime, value)
+ }
+ if _u.mutation.PromoCodeCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.PromoCodeTable,
+ Columns: []string{promocodeusage.PromoCodeColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocode.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.PromoCodeIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.PromoCodeTable,
+ Columns: []string{promocodeusage.PromoCodeColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocode.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.UserCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.UserTable,
+ Columns: []string{promocodeusage.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.UserIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.M2O,
+ Inverse: true,
+ Table: promocodeusage.UserTable,
+ Columns: []string{promocodeusage.UserColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges.Add = append(_spec.Edges.Add, edge)
+ }
+ _node = &PromoCodeUsage{config: _u.config}
+ _spec.Assign = _node.assignValues
+ _spec.ScanValues = _node.scanValues
+ if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
+ if _, ok := err.(*sqlgraph.NotFoundError); ok {
+ err = &NotFoundError{promocodeusage.Label}
+ } else if sqlgraph.IsConstraintError(err) {
+ err = &ConstraintError{msg: err.Error(), wrap: err}
+ }
+ return nil, err
+ }
+ _u.mutation.done = true
+ return _node, nil
+}
diff --git a/backend/ent/proxy_query.go b/backend/ent/proxy_query.go
index 1358eed2..b817d139 100644
--- a/backend/ent/proxy_query.go
+++ b/backend/ent/proxy_query.go
@@ -9,6 +9,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -25,6 +26,7 @@ type ProxyQuery struct {
inters []Interceptor
predicates []predicate.Proxy
withAccounts *AccountQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -384,6 +386,9 @@ func (_q *ProxyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Proxy,
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -439,6 +444,9 @@ func (_q *ProxyQuery) loadAccounts(ctx context.Context, query *AccountQuery, nod
func (_q *ProxyQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -501,6 +509,9 @@ func (_q *ProxyQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -518,6 +529,32 @@ func (_q *ProxyQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *ProxyQuery) ForUpdate(opts ...sql.LockOption) *ProxyQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *ProxyQuery) ForShare(opts ...sql.LockOption) *ProxyQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// ProxyGroupBy is the group-by builder for Proxy entities.
type ProxyGroupBy struct {
selector
diff --git a/backend/ent/redeemcode_query.go b/backend/ent/redeemcode_query.go
index 442bfe81..f5b8baef 100644
--- a/backend/ent/redeemcode_query.go
+++ b/backend/ent/redeemcode_query.go
@@ -8,6 +8,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -26,6 +27,7 @@ type RedeemCodeQuery struct {
predicates []predicate.RedeemCode
withUser *UserQuery
withGroup *GroupQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -420,6 +422,9 @@ func (_q *RedeemCodeQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*R
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -511,6 +516,9 @@ func (_q *RedeemCodeQuery) loadGroup(ctx context.Context, query *GroupQuery, nod
func (_q *RedeemCodeQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -579,6 +587,9 @@ func (_q *RedeemCodeQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -596,6 +607,32 @@ func (_q *RedeemCodeQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *RedeemCodeQuery) ForUpdate(opts ...sql.LockOption) *RedeemCodeQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *RedeemCodeQuery) ForShare(opts ...sql.LockOption) *RedeemCodeQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// RedeemCodeGroupBy is the group-by builder for RedeemCode entities.
type RedeemCodeGroupBy struct {
selector
diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go
index b82f2e6c..ad1aa626 100644
--- a/backend/ent/runtime/runtime.go
+++ b/backend/ent/runtime/runtime.go
@@ -9,6 +9,8 @@ import (
"github.com/Wei-Shaw/sub2api/ent/accountgroup"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/schema"
@@ -274,6 +276,60 @@ func init() {
groupDescClaudeCodeOnly := groupFields[14].Descriptor()
// group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field.
group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool)
+ promocodeFields := schema.PromoCode{}.Fields()
+ _ = promocodeFields
+ // promocodeDescCode is the schema descriptor for code field.
+ promocodeDescCode := promocodeFields[0].Descriptor()
+ // promocode.CodeValidator is a validator for the "code" field. It is called by the builders before save.
+ promocode.CodeValidator = func() func(string) error {
+ validators := promocodeDescCode.Validators
+ fns := [...]func(string) error{
+ validators[0].(func(string) error),
+ validators[1].(func(string) error),
+ }
+ return func(code string) error {
+ for _, fn := range fns {
+ if err := fn(code); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ }()
+ // promocodeDescBonusAmount is the schema descriptor for bonus_amount field.
+ promocodeDescBonusAmount := promocodeFields[1].Descriptor()
+ // promocode.DefaultBonusAmount holds the default value on creation for the bonus_amount field.
+ promocode.DefaultBonusAmount = promocodeDescBonusAmount.Default.(float64)
+ // promocodeDescMaxUses is the schema descriptor for max_uses field.
+ promocodeDescMaxUses := promocodeFields[2].Descriptor()
+ // promocode.DefaultMaxUses holds the default value on creation for the max_uses field.
+ promocode.DefaultMaxUses = promocodeDescMaxUses.Default.(int)
+ // promocodeDescUsedCount is the schema descriptor for used_count field.
+ promocodeDescUsedCount := promocodeFields[3].Descriptor()
+ // promocode.DefaultUsedCount holds the default value on creation for the used_count field.
+ promocode.DefaultUsedCount = promocodeDescUsedCount.Default.(int)
+ // promocodeDescStatus is the schema descriptor for status field.
+ promocodeDescStatus := promocodeFields[4].Descriptor()
+ // promocode.DefaultStatus holds the default value on creation for the status field.
+ promocode.DefaultStatus = promocodeDescStatus.Default.(string)
+ // promocode.StatusValidator is a validator for the "status" field. It is called by the builders before save.
+ promocode.StatusValidator = promocodeDescStatus.Validators[0].(func(string) error)
+ // promocodeDescCreatedAt is the schema descriptor for created_at field.
+ promocodeDescCreatedAt := promocodeFields[7].Descriptor()
+ // promocode.DefaultCreatedAt holds the default value on creation for the created_at field.
+ promocode.DefaultCreatedAt = promocodeDescCreatedAt.Default.(func() time.Time)
+ // promocodeDescUpdatedAt is the schema descriptor for updated_at field.
+ promocodeDescUpdatedAt := promocodeFields[8].Descriptor()
+ // promocode.DefaultUpdatedAt holds the default value on creation for the updated_at field.
+ promocode.DefaultUpdatedAt = promocodeDescUpdatedAt.Default.(func() time.Time)
+ // promocode.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
+ promocode.UpdateDefaultUpdatedAt = promocodeDescUpdatedAt.UpdateDefault.(func() time.Time)
+ promocodeusageFields := schema.PromoCodeUsage{}.Fields()
+ _ = promocodeusageFields
+ // promocodeusageDescUsedAt is the schema descriptor for used_at field.
+ promocodeusageDescUsedAt := promocodeusageFields[3].Descriptor()
+ // promocodeusage.DefaultUsedAt holds the default value on creation for the used_at field.
+ promocodeusage.DefaultUsedAt = promocodeusageDescUsedAt.Default.(func() time.Time)
proxyMixin := schema.Proxy{}.Mixin()
proxyMixinHooks1 := proxyMixin[1].Hooks()
proxy.Hooks[0] = proxyMixinHooks1[0]
diff --git a/backend/ent/schema/promo_code.go b/backend/ent/schema/promo_code.go
new file mode 100644
index 00000000..c3bb824b
--- /dev/null
+++ b/backend/ent/schema/promo_code.go
@@ -0,0 +1,87 @@
+package schema
+
+import (
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/service"
+
+ "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"
+)
+
+// PromoCode holds the schema definition for the PromoCode entity.
+//
+// 注册优惠码:用户注册时使用,可获得赠送余额
+// 与 RedeemCode 不同,PromoCode 支持多次使用(有使用次数限制)
+//
+// 删除策略:硬删除
+type PromoCode struct {
+ ent.Schema
+}
+
+func (PromoCode) Annotations() []schema.Annotation {
+ return []schema.Annotation{
+ entsql.Annotation{Table: "promo_codes"},
+ }
+}
+
+func (PromoCode) Fields() []ent.Field {
+ return []ent.Field{
+ field.String("code").
+ MaxLen(32).
+ NotEmpty().
+ Unique().
+ Comment("优惠码"),
+ field.Float("bonus_amount").
+ SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
+ Default(0).
+ Comment("赠送余额金额"),
+ field.Int("max_uses").
+ Default(0).
+ Comment("最大使用次数,0表示无限制"),
+ field.Int("used_count").
+ Default(0).
+ Comment("已使用次数"),
+ field.String("status").
+ MaxLen(20).
+ Default(service.PromoCodeStatusActive).
+ Comment("状态: active, disabled"),
+ field.Time("expires_at").
+ Optional().
+ Nillable().
+ SchemaType(map[string]string{dialect.Postgres: "timestamptz"}).
+ Comment("过期时间,null表示永不过期"),
+ field.String("notes").
+ Optional().
+ Nillable().
+ SchemaType(map[string]string{dialect.Postgres: "text"}).
+ Comment("备注"),
+ field.Time("created_at").
+ Immutable().
+ Default(time.Now).
+ SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
+ field.Time("updated_at").
+ Default(time.Now).
+ UpdateDefault(time.Now).
+ SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
+ }
+}
+
+func (PromoCode) Edges() []ent.Edge {
+ return []ent.Edge{
+ edge.To("usage_records", PromoCodeUsage.Type),
+ }
+}
+
+func (PromoCode) Indexes() []ent.Index {
+ return []ent.Index{
+ // code 字段已在 Fields() 中声明 Unique(),无需重复索引
+ index.Fields("status"),
+ index.Fields("expires_at"),
+ }
+}
diff --git a/backend/ent/schema/promo_code_usage.go b/backend/ent/schema/promo_code_usage.go
new file mode 100644
index 00000000..28fbabea
--- /dev/null
+++ b/backend/ent/schema/promo_code_usage.go
@@ -0,0 +1,66 @@
+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"
+)
+
+// PromoCodeUsage holds the schema definition for the PromoCodeUsage entity.
+//
+// 优惠码使用记录:记录每个用户使用优惠码的情况
+type PromoCodeUsage struct {
+ ent.Schema
+}
+
+func (PromoCodeUsage) Annotations() []schema.Annotation {
+ return []schema.Annotation{
+ entsql.Annotation{Table: "promo_code_usages"},
+ }
+}
+
+func (PromoCodeUsage) Fields() []ent.Field {
+ return []ent.Field{
+ field.Int64("promo_code_id").
+ Comment("优惠码ID"),
+ field.Int64("user_id").
+ Comment("使用用户ID"),
+ field.Float("bonus_amount").
+ SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
+ Comment("实际赠送金额"),
+ field.Time("used_at").
+ Default(time.Now).
+ SchemaType(map[string]string{dialect.Postgres: "timestamptz"}).
+ Comment("使用时间"),
+ }
+}
+
+func (PromoCodeUsage) Edges() []ent.Edge {
+ return []ent.Edge{
+ edge.From("promo_code", PromoCode.Type).
+ Ref("usage_records").
+ Field("promo_code_id").
+ Required().
+ Unique(),
+ edge.From("user", User.Type).
+ Ref("promo_code_usages").
+ Field("user_id").
+ Required().
+ Unique(),
+ }
+}
+
+func (PromoCodeUsage) Indexes() []ent.Index {
+ return []ent.Index{
+ index.Fields("promo_code_id"),
+ index.Fields("user_id"),
+ // 每个用户每个优惠码只能使用一次
+ index.Fields("promo_code_id", "user_id").Unique(),
+ }
+}
diff --git a/backend/ent/schema/user.go b/backend/ent/schema/user.go
index 11fecdfd..79dc2286 100644
--- a/backend/ent/schema/user.go
+++ b/backend/ent/schema/user.go
@@ -74,6 +74,7 @@ func (User) Edges() []ent.Edge {
Through("user_allowed_groups", UserAllowedGroup.Type),
edge.To("usage_logs", UsageLog.Type),
edge.To("attribute_values", UserAttributeValue.Type),
+ edge.To("promo_code_usages", PromoCodeUsage.Type),
}
}
diff --git a/backend/ent/setting_query.go b/backend/ent/setting_query.go
index e9dda6f5..38eb9462 100644
--- a/backend/ent/setting_query.go
+++ b/backend/ent/setting_query.go
@@ -8,6 +8,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -22,6 +23,7 @@ type SettingQuery struct {
order []setting.OrderOption
inters []Interceptor
predicates []predicate.Setting
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -343,6 +345,9 @@ func (_q *SettingQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Sett
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -357,6 +362,9 @@ func (_q *SettingQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Sett
func (_q *SettingQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -419,6 +427,9 @@ func (_q *SettingQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -436,6 +447,32 @@ func (_q *SettingQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *SettingQuery) ForUpdate(opts ...sql.LockOption) *SettingQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *SettingQuery) ForShare(opts ...sql.LockOption) *SettingQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// SettingGroupBy is the group-by builder for Setting entities.
type SettingGroupBy struct {
selector
diff --git a/backend/ent/tx.go b/backend/ent/tx.go
index e45204c0..56df121a 100644
--- a/backend/ent/tx.go
+++ b/backend/ent/tx.go
@@ -22,6 +22,10 @@ type Tx struct {
AccountGroup *AccountGroupClient
// Group is the client for interacting with the Group builders.
Group *GroupClient
+ // PromoCode is the client for interacting with the PromoCode builders.
+ PromoCode *PromoCodeClient
+ // PromoCodeUsage is the client for interacting with the PromoCodeUsage builders.
+ PromoCodeUsage *PromoCodeUsageClient
// Proxy is the client for interacting with the Proxy builders.
Proxy *ProxyClient
// RedeemCode is the client for interacting with the RedeemCode builders.
@@ -175,6 +179,8 @@ func (tx *Tx) init() {
tx.Account = NewAccountClient(tx.config)
tx.AccountGroup = NewAccountGroupClient(tx.config)
tx.Group = NewGroupClient(tx.config)
+ tx.PromoCode = NewPromoCodeClient(tx.config)
+ tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config)
tx.Proxy = NewProxyClient(tx.config)
tx.RedeemCode = NewRedeemCodeClient(tx.config)
tx.Setting = NewSettingClient(tx.config)
diff --git a/backend/ent/usagelog_query.go b/backend/ent/usagelog_query.go
index de64171a..c709bde0 100644
--- a/backend/ent/usagelog_query.go
+++ b/backend/ent/usagelog_query.go
@@ -8,6 +8,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -32,6 +33,7 @@ type UsageLogQuery struct {
withAccount *AccountQuery
withGroup *GroupQuery
withSubscription *UserSubscriptionQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -531,6 +533,9 @@ func (_q *UsageLogQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Usa
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -727,6 +732,9 @@ func (_q *UsageLogQuery) loadSubscription(ctx context.Context, query *UserSubscr
func (_q *UsageLogQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -804,6 +812,9 @@ func (_q *UsageLogQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -821,6 +832,32 @@ func (_q *UsageLogQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *UsageLogQuery) ForUpdate(opts ...sql.LockOption) *UsageLogQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *UsageLogQuery) ForShare(opts ...sql.LockOption) *UsageLogQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// UsageLogGroupBy is the group-by builder for UsageLog entities.
type UsageLogGroupBy struct {
selector
diff --git a/backend/ent/user.go b/backend/ent/user.go
index 20036475..0b9a48cc 100644
--- a/backend/ent/user.go
+++ b/backend/ent/user.go
@@ -61,11 +61,13 @@ type UserEdges struct {
UsageLogs []*UsageLog `json:"usage_logs,omitempty"`
// AttributeValues holds the value of the attribute_values edge.
AttributeValues []*UserAttributeValue `json:"attribute_values,omitempty"`
+ // PromoCodeUsages holds the value of the promo_code_usages edge.
+ PromoCodeUsages []*PromoCodeUsage `json:"promo_code_usages,omitempty"`
// UserAllowedGroups holds the value of the user_allowed_groups edge.
UserAllowedGroups []*UserAllowedGroup `json:"user_allowed_groups,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
- loadedTypes [8]bool
+ loadedTypes [9]bool
}
// APIKeysOrErr returns the APIKeys value or an error if the edge
@@ -131,10 +133,19 @@ func (e UserEdges) AttributeValuesOrErr() ([]*UserAttributeValue, error) {
return nil, &NotLoadedError{edge: "attribute_values"}
}
+// PromoCodeUsagesOrErr returns the PromoCodeUsages value or an error if the edge
+// was not loaded in eager-loading.
+func (e UserEdges) PromoCodeUsagesOrErr() ([]*PromoCodeUsage, error) {
+ if e.loadedTypes[7] {
+ return e.PromoCodeUsages, nil
+ }
+ return nil, &NotLoadedError{edge: "promo_code_usages"}
+}
+
// UserAllowedGroupsOrErr returns the UserAllowedGroups value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) UserAllowedGroupsOrErr() ([]*UserAllowedGroup, error) {
- if e.loadedTypes[7] {
+ if e.loadedTypes[8] {
return e.UserAllowedGroups, nil
}
return nil, &NotLoadedError{edge: "user_allowed_groups"}
@@ -289,6 +300,11 @@ func (_m *User) QueryAttributeValues() *UserAttributeValueQuery {
return NewUserClient(_m.config).QueryAttributeValues(_m)
}
+// QueryPromoCodeUsages queries the "promo_code_usages" edge of the User entity.
+func (_m *User) QueryPromoCodeUsages() *PromoCodeUsageQuery {
+ return NewUserClient(_m.config).QueryPromoCodeUsages(_m)
+}
+
// QueryUserAllowedGroups queries the "user_allowed_groups" edge of the User entity.
func (_m *User) QueryUserAllowedGroups() *UserAllowedGroupQuery {
return NewUserClient(_m.config).QueryUserAllowedGroups(_m)
diff --git a/backend/ent/user/user.go b/backend/ent/user/user.go
index a6871c5d..1be1d871 100644
--- a/backend/ent/user/user.go
+++ b/backend/ent/user/user.go
@@ -51,6 +51,8 @@ const (
EdgeUsageLogs = "usage_logs"
// EdgeAttributeValues holds the string denoting the attribute_values edge name in mutations.
EdgeAttributeValues = "attribute_values"
+ // EdgePromoCodeUsages holds the string denoting the promo_code_usages edge name in mutations.
+ EdgePromoCodeUsages = "promo_code_usages"
// EdgeUserAllowedGroups holds the string denoting the user_allowed_groups edge name in mutations.
EdgeUserAllowedGroups = "user_allowed_groups"
// Table holds the table name of the user in the database.
@@ -102,6 +104,13 @@ const (
AttributeValuesInverseTable = "user_attribute_values"
// AttributeValuesColumn is the table column denoting the attribute_values relation/edge.
AttributeValuesColumn = "user_id"
+ // PromoCodeUsagesTable is the table that holds the promo_code_usages relation/edge.
+ PromoCodeUsagesTable = "promo_code_usages"
+ // PromoCodeUsagesInverseTable is the table name for the PromoCodeUsage entity.
+ // It exists in this package in order to avoid circular dependency with the "promocodeusage" package.
+ PromoCodeUsagesInverseTable = "promo_code_usages"
+ // PromoCodeUsagesColumn is the table column denoting the promo_code_usages relation/edge.
+ PromoCodeUsagesColumn = "user_id"
// UserAllowedGroupsTable is the table that holds the user_allowed_groups relation/edge.
UserAllowedGroupsTable = "user_allowed_groups"
// UserAllowedGroupsInverseTable is the table name for the UserAllowedGroup entity.
@@ -342,6 +351,20 @@ func ByAttributeValues(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
}
}
+// ByPromoCodeUsagesCount orders the results by promo_code_usages count.
+func ByPromoCodeUsagesCount(opts ...sql.OrderTermOption) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborsCount(s, newPromoCodeUsagesStep(), opts...)
+ }
+}
+
+// ByPromoCodeUsages orders the results by promo_code_usages terms.
+func ByPromoCodeUsages(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
+ return func(s *sql.Selector) {
+ sqlgraph.OrderByNeighborTerms(s, newPromoCodeUsagesStep(), append([]sql.OrderTerm{term}, terms...)...)
+ }
+}
+
// ByUserAllowedGroupsCount orders the results by user_allowed_groups count.
func ByUserAllowedGroupsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
@@ -404,6 +427,13 @@ func newAttributeValuesStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, AttributeValuesTable, AttributeValuesColumn),
)
}
+func newPromoCodeUsagesStep() *sqlgraph.Step {
+ return sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.To(PromoCodeUsagesInverseTable, FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, PromoCodeUsagesTable, PromoCodeUsagesColumn),
+ )
+}
func newUserAllowedGroupsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
diff --git a/backend/ent/user/where.go b/backend/ent/user/where.go
index 38812770..6a460f10 100644
--- a/backend/ent/user/where.go
+++ b/backend/ent/user/where.go
@@ -871,6 +871,29 @@ func HasAttributeValuesWith(preds ...predicate.UserAttributeValue) predicate.Use
})
}
+// HasPromoCodeUsages applies the HasEdge predicate on the "promo_code_usages" edge.
+func HasPromoCodeUsages() predicate.User {
+ return predicate.User(func(s *sql.Selector) {
+ step := sqlgraph.NewStep(
+ sqlgraph.From(Table, FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, PromoCodeUsagesTable, PromoCodeUsagesColumn),
+ )
+ sqlgraph.HasNeighbors(s, step)
+ })
+}
+
+// HasPromoCodeUsagesWith applies the HasEdge predicate on the "promo_code_usages" edge with a given conditions (other predicates).
+func HasPromoCodeUsagesWith(preds ...predicate.PromoCodeUsage) predicate.User {
+ return predicate.User(func(s *sql.Selector) {
+ step := newPromoCodeUsagesStep()
+ sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
+ for _, p := range preds {
+ p(s)
+ }
+ })
+ })
+}
+
// HasUserAllowedGroups applies the HasEdge predicate on the "user_allowed_groups" edge.
func HasUserAllowedGroups() predicate.User {
return predicate.User(func(s *sql.Selector) {
diff --git a/backend/ent/user_create.go b/backend/ent/user_create.go
index 4ce48d4b..e12e476c 100644
--- a/backend/ent/user_create.go
+++ b/backend/ent/user_create.go
@@ -13,6 +13,7 @@ import (
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
@@ -271,6 +272,21 @@ func (_c *UserCreate) AddAttributeValues(v ...*UserAttributeValue) *UserCreate {
return _c.AddAttributeValueIDs(ids...)
}
+// AddPromoCodeUsageIDs adds the "promo_code_usages" edge to the PromoCodeUsage entity by IDs.
+func (_c *UserCreate) AddPromoCodeUsageIDs(ids ...int64) *UserCreate {
+ _c.mutation.AddPromoCodeUsageIDs(ids...)
+ return _c
+}
+
+// AddPromoCodeUsages adds the "promo_code_usages" edges to the PromoCodeUsage entity.
+func (_c *UserCreate) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserCreate {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _c.AddPromoCodeUsageIDs(ids...)
+}
+
// Mutation returns the UserMutation object of the builder.
func (_c *UserCreate) Mutation() *UserMutation {
return _c.mutation
@@ -593,6 +609,22 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
+ if nodes := _c.mutation.PromoCodeUsagesIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges = append(_spec.Edges, edge)
+ }
return _node, _spec
}
diff --git a/backend/ent/user_query.go b/backend/ent/user_query.go
index 0d65a2dd..e66e2dc8 100644
--- a/backend/ent/user_query.go
+++ b/backend/ent/user_query.go
@@ -9,12 +9,14 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
@@ -37,7 +39,9 @@ type UserQuery struct {
withAllowedGroups *GroupQuery
withUsageLogs *UsageLogQuery
withAttributeValues *UserAttributeValueQuery
+ withPromoCodeUsages *PromoCodeUsageQuery
withUserAllowedGroups *UserAllowedGroupQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -228,6 +232,28 @@ func (_q *UserQuery) QueryAttributeValues() *UserAttributeValueQuery {
return query
}
+// QueryPromoCodeUsages chains the current query on the "promo_code_usages" edge.
+func (_q *UserQuery) QueryPromoCodeUsages() *PromoCodeUsageQuery {
+ query := (&PromoCodeUsageClient{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(user.Table, user.FieldID, selector),
+ sqlgraph.To(promocodeusage.Table, promocodeusage.FieldID),
+ sqlgraph.Edge(sqlgraph.O2M, false, user.PromoCodeUsagesTable, user.PromoCodeUsagesColumn),
+ )
+ fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
+ return fromU, nil
+ }
+ return query
+}
+
// QueryUserAllowedGroups chains the current query on the "user_allowed_groups" edge.
func (_q *UserQuery) QueryUserAllowedGroups() *UserAllowedGroupQuery {
query := (&UserAllowedGroupClient{config: _q.config}).Query()
@@ -449,6 +475,7 @@ func (_q *UserQuery) Clone() *UserQuery {
withAllowedGroups: _q.withAllowedGroups.Clone(),
withUsageLogs: _q.withUsageLogs.Clone(),
withAttributeValues: _q.withAttributeValues.Clone(),
+ withPromoCodeUsages: _q.withPromoCodeUsages.Clone(),
withUserAllowedGroups: _q.withUserAllowedGroups.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
@@ -533,6 +560,17 @@ func (_q *UserQuery) WithAttributeValues(opts ...func(*UserAttributeValueQuery))
return _q
}
+// WithPromoCodeUsages tells the query-builder to eager-load the nodes that are connected to
+// the "promo_code_usages" edge. The optional arguments are used to configure the query builder of the edge.
+func (_q *UserQuery) WithPromoCodeUsages(opts ...func(*PromoCodeUsageQuery)) *UserQuery {
+ query := (&PromoCodeUsageClient{config: _q.config}).Query()
+ for _, opt := range opts {
+ opt(query)
+ }
+ _q.withPromoCodeUsages = query
+ return _q
+}
+
// WithUserAllowedGroups tells the query-builder to eager-load the nodes that are connected to
// the "user_allowed_groups" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *UserQuery) WithUserAllowedGroups(opts ...func(*UserAllowedGroupQuery)) *UserQuery {
@@ -622,7 +660,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
var (
nodes = []*User{}
_spec = _q.querySpec()
- loadedTypes = [8]bool{
+ loadedTypes = [9]bool{
_q.withAPIKeys != nil,
_q.withRedeemCodes != nil,
_q.withSubscriptions != nil,
@@ -630,6 +668,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
_q.withAllowedGroups != nil,
_q.withUsageLogs != nil,
_q.withAttributeValues != nil,
+ _q.withPromoCodeUsages != nil,
_q.withUserAllowedGroups != nil,
}
)
@@ -642,6 +681,9 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -702,6 +744,13 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nil, err
}
}
+ if query := _q.withPromoCodeUsages; query != nil {
+ if err := _q.loadPromoCodeUsages(ctx, query, nodes,
+ func(n *User) { n.Edges.PromoCodeUsages = []*PromoCodeUsage{} },
+ func(n *User, e *PromoCodeUsage) { n.Edges.PromoCodeUsages = append(n.Edges.PromoCodeUsages, e) }); err != nil {
+ return nil, err
+ }
+ }
if query := _q.withUserAllowedGroups; query != nil {
if err := _q.loadUserAllowedGroups(ctx, query, nodes,
func(n *User) { n.Edges.UserAllowedGroups = []*UserAllowedGroup{} },
@@ -959,6 +1008,36 @@ func (_q *UserQuery) loadAttributeValues(ctx context.Context, query *UserAttribu
}
return nil
}
+func (_q *UserQuery) loadPromoCodeUsages(ctx context.Context, query *PromoCodeUsageQuery, nodes []*User, init func(*User), assign func(*User, *PromoCodeUsage)) error {
+ fks := make([]driver.Value, 0, len(nodes))
+ nodeids := make(map[int64]*User)
+ 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(promocodeusage.FieldUserID)
+ }
+ query.Where(predicate.PromoCodeUsage(func(s *sql.Selector) {
+ s.Where(sql.InValues(s.C(user.PromoCodeUsagesColumn), fks...))
+ }))
+ neighbors, err := query.All(ctx)
+ if err != nil {
+ return err
+ }
+ for _, n := range neighbors {
+ fk := n.UserID
+ node, ok := nodeids[fk]
+ if !ok {
+ return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID)
+ }
+ assign(node, n)
+ }
+ return nil
+}
func (_q *UserQuery) loadUserAllowedGroups(ctx context.Context, query *UserAllowedGroupQuery, nodes []*User, init func(*User), assign func(*User, *UserAllowedGroup)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*User)
@@ -992,6 +1071,9 @@ func (_q *UserQuery) loadUserAllowedGroups(ctx context.Context, query *UserAllow
func (_q *UserQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -1054,6 +1136,9 @@ func (_q *UserQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -1071,6 +1156,32 @@ func (_q *UserQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *UserQuery) ForUpdate(opts ...sql.LockOption) *UserQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *UserQuery) ForShare(opts ...sql.LockOption) *UserQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// UserGroupBy is the group-by builder for User entities.
type UserGroupBy struct {
selector
diff --git a/backend/ent/user_update.go b/backend/ent/user_update.go
index 49ddf493..cf189fea 100644
--- a/backend/ent/user_update.go
+++ b/backend/ent/user_update.go
@@ -14,6 +14,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
"github.com/Wei-Shaw/sub2api/ent/user"
@@ -291,6 +292,21 @@ func (_u *UserUpdate) AddAttributeValues(v ...*UserAttributeValue) *UserUpdate {
return _u.AddAttributeValueIDs(ids...)
}
+// AddPromoCodeUsageIDs adds the "promo_code_usages" edge to the PromoCodeUsage entity by IDs.
+func (_u *UserUpdate) AddPromoCodeUsageIDs(ids ...int64) *UserUpdate {
+ _u.mutation.AddPromoCodeUsageIDs(ids...)
+ return _u
+}
+
+// AddPromoCodeUsages adds the "promo_code_usages" edges to the PromoCodeUsage entity.
+func (_u *UserUpdate) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.AddPromoCodeUsageIDs(ids...)
+}
+
// Mutation returns the UserMutation object of the builder.
func (_u *UserUpdate) Mutation() *UserMutation {
return _u.mutation
@@ -443,6 +459,27 @@ func (_u *UserUpdate) RemoveAttributeValues(v ...*UserAttributeValue) *UserUpdat
return _u.RemoveAttributeValueIDs(ids...)
}
+// ClearPromoCodeUsages clears all "promo_code_usages" edges to the PromoCodeUsage entity.
+func (_u *UserUpdate) ClearPromoCodeUsages() *UserUpdate {
+ _u.mutation.ClearPromoCodeUsages()
+ return _u
+}
+
+// RemovePromoCodeUsageIDs removes the "promo_code_usages" edge to PromoCodeUsage entities by IDs.
+func (_u *UserUpdate) RemovePromoCodeUsageIDs(ids ...int64) *UserUpdate {
+ _u.mutation.RemovePromoCodeUsageIDs(ids...)
+ return _u
+}
+
+// RemovePromoCodeUsages removes "promo_code_usages" edges to PromoCodeUsage entities.
+func (_u *UserUpdate) RemovePromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.RemovePromoCodeUsageIDs(ids...)
+}
+
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *UserUpdate) Save(ctx context.Context) (int, error) {
if err := _u.defaults(); err != nil {
@@ -893,6 +930,51 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
+ if _u.mutation.PromoCodeUsagesCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.RemovedPromoCodeUsagesIDs(); len(nodes) > 0 && !_u.mutation.PromoCodeUsagesCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.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.PromoCodeUsagesIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.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{user.Label}
@@ -1170,6 +1252,21 @@ func (_u *UserUpdateOne) AddAttributeValues(v ...*UserAttributeValue) *UserUpdat
return _u.AddAttributeValueIDs(ids...)
}
+// AddPromoCodeUsageIDs adds the "promo_code_usages" edge to the PromoCodeUsage entity by IDs.
+func (_u *UserUpdateOne) AddPromoCodeUsageIDs(ids ...int64) *UserUpdateOne {
+ _u.mutation.AddPromoCodeUsageIDs(ids...)
+ return _u
+}
+
+// AddPromoCodeUsages adds the "promo_code_usages" edges to the PromoCodeUsage entity.
+func (_u *UserUpdateOne) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserUpdateOne {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.AddPromoCodeUsageIDs(ids...)
+}
+
// Mutation returns the UserMutation object of the builder.
func (_u *UserUpdateOne) Mutation() *UserMutation {
return _u.mutation
@@ -1322,6 +1419,27 @@ func (_u *UserUpdateOne) RemoveAttributeValues(v ...*UserAttributeValue) *UserUp
return _u.RemoveAttributeValueIDs(ids...)
}
+// ClearPromoCodeUsages clears all "promo_code_usages" edges to the PromoCodeUsage entity.
+func (_u *UserUpdateOne) ClearPromoCodeUsages() *UserUpdateOne {
+ _u.mutation.ClearPromoCodeUsages()
+ return _u
+}
+
+// RemovePromoCodeUsageIDs removes the "promo_code_usages" edge to PromoCodeUsage entities by IDs.
+func (_u *UserUpdateOne) RemovePromoCodeUsageIDs(ids ...int64) *UserUpdateOne {
+ _u.mutation.RemovePromoCodeUsageIDs(ids...)
+ return _u
+}
+
+// RemovePromoCodeUsages removes "promo_code_usages" edges to PromoCodeUsage entities.
+func (_u *UserUpdateOne) RemovePromoCodeUsages(v ...*PromoCodeUsage) *UserUpdateOne {
+ ids := make([]int64, len(v))
+ for i := range v {
+ ids[i] = v[i].ID
+ }
+ return _u.RemovePromoCodeUsageIDs(ids...)
+}
+
// Where appends a list predicates to the UserUpdate builder.
func (_u *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
_u.mutation.Where(ps...)
@@ -1802,6 +1920,51 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
+ if _u.mutation.PromoCodeUsagesCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ _spec.Edges.Clear = append(_spec.Edges.Clear, edge)
+ }
+ if nodes := _u.mutation.RemovedPromoCodeUsagesIDs(); len(nodes) > 0 && !_u.mutation.PromoCodeUsagesCleared() {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.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.PromoCodeUsagesIDs(); len(nodes) > 0 {
+ edge := &sqlgraph.EdgeSpec{
+ Rel: sqlgraph.O2M,
+ Inverse: false,
+ Table: user.PromoCodeUsagesTable,
+ Columns: []string{user.PromoCodeUsagesColumn},
+ Bidi: false,
+ Target: &sqlgraph.EdgeTarget{
+ IDSpec: sqlgraph.NewFieldSpec(promocodeusage.FieldID, field.TypeInt64),
+ },
+ }
+ for _, k := range nodes {
+ edge.Target.Nodes = append(edge.Target.Nodes, k)
+ }
+ _spec.Edges.Add = append(_spec.Edges.Add, edge)
+ }
_node = &User{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
diff --git a/backend/ent/userallowedgroup_query.go b/backend/ent/userallowedgroup_query.go
index da2c19a7..527ddc77 100644
--- a/backend/ent/userallowedgroup_query.go
+++ b/backend/ent/userallowedgroup_query.go
@@ -8,6 +8,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/group"
@@ -25,6 +26,7 @@ type UserAllowedGroupQuery struct {
predicates []predicate.UserAllowedGroup
withUser *UserQuery
withGroup *GroupQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -347,6 +349,9 @@ func (_q *UserAllowedGroupQuery) sqlAll(ctx context.Context, hooks ...queryHook)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -432,6 +437,9 @@ func (_q *UserAllowedGroupQuery) loadGroup(ctx context.Context, query *GroupQuer
func (_q *UserAllowedGroupQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Unique = false
_spec.Node.Columns = nil
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
@@ -495,6 +503,9 @@ func (_q *UserAllowedGroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -512,6 +523,32 @@ func (_q *UserAllowedGroupQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *UserAllowedGroupQuery) ForUpdate(opts ...sql.LockOption) *UserAllowedGroupQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *UserAllowedGroupQuery) ForShare(opts ...sql.LockOption) *UserAllowedGroupQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// UserAllowedGroupGroupBy is the group-by builder for UserAllowedGroup entities.
type UserAllowedGroupGroupBy struct {
selector
diff --git a/backend/ent/userattributedefinition_query.go b/backend/ent/userattributedefinition_query.go
index 9022d306..0727b47c 100644
--- a/backend/ent/userattributedefinition_query.go
+++ b/backend/ent/userattributedefinition_query.go
@@ -9,6 +9,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -25,6 +26,7 @@ type UserAttributeDefinitionQuery struct {
inters []Interceptor
predicates []predicate.UserAttributeDefinition
withValues *UserAttributeValueQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -384,6 +386,9 @@ func (_q *UserAttributeDefinitionQuery) sqlAll(ctx context.Context, hooks ...que
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -436,6 +441,9 @@ func (_q *UserAttributeDefinitionQuery) loadValues(ctx context.Context, query *U
func (_q *UserAttributeDefinitionQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -498,6 +506,9 @@ func (_q *UserAttributeDefinitionQuery) sqlQuery(ctx context.Context) *sql.Selec
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -515,6 +526,32 @@ func (_q *UserAttributeDefinitionQuery) sqlQuery(ctx context.Context) *sql.Selec
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *UserAttributeDefinitionQuery) ForUpdate(opts ...sql.LockOption) *UserAttributeDefinitionQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *UserAttributeDefinitionQuery) ForShare(opts ...sql.LockOption) *UserAttributeDefinitionQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// UserAttributeDefinitionGroupBy is the group-by builder for UserAttributeDefinition entities.
type UserAttributeDefinitionGroupBy struct {
selector
diff --git a/backend/ent/userattributevalue_query.go b/backend/ent/userattributevalue_query.go
index babfc9a9..a7c6b74a 100644
--- a/backend/ent/userattributevalue_query.go
+++ b/backend/ent/userattributevalue_query.go
@@ -8,6 +8,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -26,6 +27,7 @@ type UserAttributeValueQuery struct {
predicates []predicate.UserAttributeValue
withUser *UserQuery
withDefinition *UserAttributeDefinitionQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -420,6 +422,9 @@ func (_q *UserAttributeValueQuery) sqlAll(ctx context.Context, hooks ...queryHoo
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -505,6 +510,9 @@ func (_q *UserAttributeValueQuery) loadDefinition(ctx context.Context, query *Us
func (_q *UserAttributeValueQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -573,6 +581,9 @@ func (_q *UserAttributeValueQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -590,6 +601,32 @@ func (_q *UserAttributeValueQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *UserAttributeValueQuery) ForUpdate(opts ...sql.LockOption) *UserAttributeValueQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *UserAttributeValueQuery) ForShare(opts ...sql.LockOption) *UserAttributeValueQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// UserAttributeValueGroupBy is the group-by builder for UserAttributeValue entities.
type UserAttributeValueGroupBy struct {
selector
diff --git a/backend/ent/usersubscription_query.go b/backend/ent/usersubscription_query.go
index 967fbddb..288b7b1d 100644
--- a/backend/ent/usersubscription_query.go
+++ b/backend/ent/usersubscription_query.go
@@ -9,6 +9,7 @@ import (
"math"
"entgo.io/ent"
+ "entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
@@ -30,6 +31,7 @@ type UserSubscriptionQuery struct {
withGroup *GroupQuery
withAssignedByUser *UserQuery
withUsageLogs *UsageLogQuery
+ modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
@@ -494,6 +496,9 @@ func (_q *UserSubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
for i := range hooks {
hooks[i](ctx, _spec)
}
@@ -657,6 +662,9 @@ func (_q *UserSubscriptionQuery) loadUsageLogs(ctx context.Context, query *Usage
func (_q *UserSubscriptionQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
+ if len(_q.modifiers) > 0 {
+ _spec.Modifiers = _q.modifiers
+ }
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
@@ -728,6 +736,9 @@ func (_q *UserSubscriptionQuery) sqlQuery(ctx context.Context) *sql.Selector {
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
+ for _, m := range _q.modifiers {
+ m(selector)
+ }
for _, p := range _q.predicates {
p(selector)
}
@@ -745,6 +756,32 @@ func (_q *UserSubscriptionQuery) sqlQuery(ctx context.Context) *sql.Selector {
return selector
}
+// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
+// updated, deleted or "selected ... for update" by other sessions, until the transaction is
+// either committed or rolled-back.
+func (_q *UserSubscriptionQuery) ForUpdate(opts ...sql.LockOption) *UserSubscriptionQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForUpdate(opts...)
+ })
+ return _q
+}
+
+// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
+// on any rows that are read. Other sessions can read the rows, but cannot modify them
+// until your transaction commits.
+func (_q *UserSubscriptionQuery) ForShare(opts ...sql.LockOption) *UserSubscriptionQuery {
+ if _q.driver.Dialect() == dialect.Postgres {
+ _q.Unique(false)
+ }
+ _q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
+ s.ForShare(opts...)
+ })
+ return _q
+}
+
// UserSubscriptionGroupBy is the group-by builder for UserSubscription entities.
type UserSubscriptionGroupBy struct {
selector
diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go
index 30a6282f..40344cd4 100644
--- a/backend/internal/config/config.go
+++ b/backend/internal/config/config.go
@@ -687,7 +687,7 @@ func setDefaults() {
viper.SetDefault("gateway.concurrency_slot_ttl_minutes", 30) // 并发槽位过期时间(支持超长请求)
viper.SetDefault("gateway.stream_data_interval_timeout", 180)
viper.SetDefault("gateway.stream_keepalive_interval", 10)
- viper.SetDefault("gateway.max_line_size", 10*1024*1024)
+ viper.SetDefault("gateway.max_line_size", 40*1024*1024)
viper.SetDefault("gateway.scheduling.sticky_session_max_waiting", 3)
viper.SetDefault("gateway.scheduling.sticky_session_wait_timeout", 45*time.Second)
viper.SetDefault("gateway.scheduling.fallback_wait_timeout", 30*time.Second)
diff --git a/backend/internal/handler/admin/promo_handler.go b/backend/internal/handler/admin/promo_handler.go
new file mode 100644
index 00000000..3eafa380
--- /dev/null
+++ b/backend/internal/handler/admin/promo_handler.go
@@ -0,0 +1,209 @@
+package admin
+
+import (
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/Wei-Shaw/sub2api/internal/handler/dto"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/response"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+
+ "github.com/gin-gonic/gin"
+)
+
+// PromoHandler handles admin promo code management
+type PromoHandler struct {
+ promoService *service.PromoService
+}
+
+// NewPromoHandler creates a new admin promo handler
+func NewPromoHandler(promoService *service.PromoService) *PromoHandler {
+ return &PromoHandler{
+ promoService: promoService,
+ }
+}
+
+// CreatePromoCodeRequest represents create promo code request
+type CreatePromoCodeRequest struct {
+ Code string `json:"code"` // 可选,为空则自动生成
+ BonusAmount float64 `json:"bonus_amount" binding:"required,min=0"` // 赠送余额
+ MaxUses int `json:"max_uses" binding:"min=0"` // 最大使用次数,0=无限
+ ExpiresAt *int64 `json:"expires_at"` // 过期时间戳(秒)
+ Notes string `json:"notes"` // 备注
+}
+
+// UpdatePromoCodeRequest represents update promo code request
+type UpdatePromoCodeRequest struct {
+ Code *string `json:"code"`
+ BonusAmount *float64 `json:"bonus_amount" binding:"omitempty,min=0"`
+ MaxUses *int `json:"max_uses" binding:"omitempty,min=0"`
+ Status *string `json:"status" binding:"omitempty,oneof=active disabled"`
+ ExpiresAt *int64 `json:"expires_at"`
+ Notes *string `json:"notes"`
+}
+
+// List handles listing all promo codes with pagination
+// GET /api/v1/admin/promo-codes
+func (h *PromoHandler) List(c *gin.Context) {
+ page, pageSize := response.ParsePagination(c)
+ status := c.Query("status")
+ search := strings.TrimSpace(c.Query("search"))
+ if len(search) > 100 {
+ search = search[:100]
+ }
+
+ params := pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ }
+
+ codes, paginationResult, err := h.promoService.List(c.Request.Context(), params, status, search)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ out := make([]dto.PromoCode, 0, len(codes))
+ for i := range codes {
+ out = append(out, *dto.PromoCodeFromService(&codes[i]))
+ }
+ response.Paginated(c, out, paginationResult.Total, page, pageSize)
+}
+
+// GetByID handles getting a promo code by ID
+// GET /api/v1/admin/promo-codes/:id
+func (h *PromoHandler) GetByID(c *gin.Context) {
+ codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
+ if err != nil {
+ response.BadRequest(c, "Invalid promo code ID")
+ return
+ }
+
+ code, err := h.promoService.GetByID(c.Request.Context(), codeID)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ response.Success(c, dto.PromoCodeFromService(code))
+}
+
+// Create handles creating a new promo code
+// POST /api/v1/admin/promo-codes
+func (h *PromoHandler) Create(c *gin.Context) {
+ var req CreatePromoCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ response.BadRequest(c, "Invalid request: "+err.Error())
+ return
+ }
+
+ input := &service.CreatePromoCodeInput{
+ Code: req.Code,
+ BonusAmount: req.BonusAmount,
+ MaxUses: req.MaxUses,
+ Notes: req.Notes,
+ }
+
+ if req.ExpiresAt != nil {
+ t := time.Unix(*req.ExpiresAt, 0)
+ input.ExpiresAt = &t
+ }
+
+ code, err := h.promoService.Create(c.Request.Context(), input)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ response.Success(c, dto.PromoCodeFromService(code))
+}
+
+// Update handles updating a promo code
+// PUT /api/v1/admin/promo-codes/:id
+func (h *PromoHandler) Update(c *gin.Context) {
+ codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
+ if err != nil {
+ response.BadRequest(c, "Invalid promo code ID")
+ return
+ }
+
+ var req UpdatePromoCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ response.BadRequest(c, "Invalid request: "+err.Error())
+ return
+ }
+
+ input := &service.UpdatePromoCodeInput{
+ Code: req.Code,
+ BonusAmount: req.BonusAmount,
+ MaxUses: req.MaxUses,
+ Status: req.Status,
+ Notes: req.Notes,
+ }
+
+ if req.ExpiresAt != nil {
+ if *req.ExpiresAt == 0 {
+ // 0 表示清除过期时间
+ input.ExpiresAt = nil
+ } else {
+ t := time.Unix(*req.ExpiresAt, 0)
+ input.ExpiresAt = &t
+ }
+ }
+
+ code, err := h.promoService.Update(c.Request.Context(), codeID, input)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ response.Success(c, dto.PromoCodeFromService(code))
+}
+
+// Delete handles deleting a promo code
+// DELETE /api/v1/admin/promo-codes/:id
+func (h *PromoHandler) Delete(c *gin.Context) {
+ codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
+ if err != nil {
+ response.BadRequest(c, "Invalid promo code ID")
+ return
+ }
+
+ err = h.promoService.Delete(c.Request.Context(), codeID)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ response.Success(c, gin.H{"message": "Promo code deleted successfully"})
+}
+
+// GetUsages handles getting usage records for a promo code
+// GET /api/v1/admin/promo-codes/:id/usages
+func (h *PromoHandler) GetUsages(c *gin.Context) {
+ codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
+ if err != nil {
+ response.BadRequest(c, "Invalid promo code ID")
+ return
+ }
+
+ page, pageSize := response.ParsePagination(c)
+ params := pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ }
+
+ usages, paginationResult, err := h.promoService.ListUsages(c.Request.Context(), codeID, params)
+ if err != nil {
+ response.ErrorFrom(c, err)
+ return
+ }
+
+ out := make([]dto.PromoCodeUsage, 0, len(usages))
+ for i := range usages {
+ out = append(out, *dto.PromoCodeUsageFromService(&usages[i]))
+ }
+ response.Paginated(c, out, paginationResult.Total, page, pageSize)
+}
diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go
index 8463367e..c5cce937 100644
--- a/backend/internal/handler/auth_handler.go
+++ b/backend/internal/handler/auth_handler.go
@@ -12,19 +12,21 @@ import (
// AuthHandler handles authentication-related requests
type AuthHandler struct {
- cfg *config.Config
- authService *service.AuthService
- userService *service.UserService
- settingSvc *service.SettingService
+ cfg *config.Config
+ authService *service.AuthService
+ userService *service.UserService
+ settingSvc *service.SettingService
+ promoService *service.PromoService
}
// NewAuthHandler creates a new AuthHandler
-func NewAuthHandler(cfg *config.Config, authService *service.AuthService, userService *service.UserService, settingService *service.SettingService) *AuthHandler {
+func NewAuthHandler(cfg *config.Config, authService *service.AuthService, userService *service.UserService, settingService *service.SettingService, promoService *service.PromoService) *AuthHandler {
return &AuthHandler{
- cfg: cfg,
- authService: authService,
- userService: userService,
- settingSvc: settingService,
+ cfg: cfg,
+ authService: authService,
+ userService: userService,
+ settingSvc: settingService,
+ promoService: promoService,
}
}
@@ -34,6 +36,7 @@ type RegisterRequest struct {
Password string `json:"password" binding:"required,min=6"`
VerifyCode string `json:"verify_code"`
TurnstileToken string `json:"turnstile_token"`
+ PromoCode string `json:"promo_code"` // 注册优惠码
}
// SendVerifyCodeRequest 发送验证码请求
@@ -79,7 +82,7 @@ func (h *AuthHandler) Register(c *gin.Context) {
}
}
- token, user, err := h.authService.RegisterWithVerification(c.Request.Context(), req.Email, req.Password, req.VerifyCode)
+ token, user, err := h.authService.RegisterWithVerification(c.Request.Context(), req.Email, req.Password, req.VerifyCode, req.PromoCode)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -174,3 +177,63 @@ func (h *AuthHandler) GetCurrentUser(c *gin.Context) {
response.Success(c, UserResponse{User: dto.UserFromService(user), RunMode: runMode})
}
+
+// ValidatePromoCodeRequest 验证优惠码请求
+type ValidatePromoCodeRequest struct {
+ Code string `json:"code" binding:"required"`
+}
+
+// ValidatePromoCodeResponse 验证优惠码响应
+type ValidatePromoCodeResponse struct {
+ Valid bool `json:"valid"`
+ BonusAmount float64 `json:"bonus_amount,omitempty"`
+ ErrorCode string `json:"error_code,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+// ValidatePromoCode 验证优惠码(公开接口,注册前调用)
+// POST /api/v1/auth/validate-promo-code
+func (h *AuthHandler) ValidatePromoCode(c *gin.Context) {
+ var req ValidatePromoCodeRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ response.BadRequest(c, "Invalid request: "+err.Error())
+ return
+ }
+
+ promoCode, err := h.promoService.ValidatePromoCode(c.Request.Context(), req.Code)
+ if err != nil {
+ // 根据错误类型返回对应的错误码
+ errorCode := "PROMO_CODE_INVALID"
+ switch err {
+ case service.ErrPromoCodeNotFound:
+ errorCode = "PROMO_CODE_NOT_FOUND"
+ case service.ErrPromoCodeExpired:
+ errorCode = "PROMO_CODE_EXPIRED"
+ case service.ErrPromoCodeDisabled:
+ errorCode = "PROMO_CODE_DISABLED"
+ case service.ErrPromoCodeMaxUsed:
+ errorCode = "PROMO_CODE_MAX_USED"
+ case service.ErrPromoCodeAlreadyUsed:
+ errorCode = "PROMO_CODE_ALREADY_USED"
+ }
+
+ response.Success(c, ValidatePromoCodeResponse{
+ Valid: false,
+ ErrorCode: errorCode,
+ })
+ return
+ }
+
+ if promoCode == nil {
+ response.Success(c, ValidatePromoCodeResponse{
+ Valid: false,
+ ErrorCode: "PROMO_CODE_INVALID",
+ })
+ return
+ }
+
+ response.Success(c, ValidatePromoCodeResponse{
+ Valid: true,
+ BonusAmount: promoCode.BonusAmount,
+ })
+}
diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go
index 85dbe6f5..6ffaedea 100644
--- a/backend/internal/handler/dto/mappers.go
+++ b/backend/internal/handler/dto/mappers.go
@@ -370,3 +370,35 @@ func BulkAssignResultFromService(r *service.BulkAssignResult) *BulkAssignResult
Errors: r.Errors,
}
}
+
+func PromoCodeFromService(pc *service.PromoCode) *PromoCode {
+ if pc == nil {
+ return nil
+ }
+ return &PromoCode{
+ ID: pc.ID,
+ Code: pc.Code,
+ BonusAmount: pc.BonusAmount,
+ MaxUses: pc.MaxUses,
+ UsedCount: pc.UsedCount,
+ Status: pc.Status,
+ ExpiresAt: pc.ExpiresAt,
+ Notes: pc.Notes,
+ CreatedAt: pc.CreatedAt,
+ UpdatedAt: pc.UpdatedAt,
+ }
+}
+
+func PromoCodeUsageFromService(u *service.PromoCodeUsage) *PromoCodeUsage {
+ if u == nil {
+ return nil
+ }
+ return &PromoCodeUsage{
+ ID: u.ID,
+ PromoCodeID: u.PromoCodeID,
+ UserID: u.UserID,
+ BonusAmount: u.BonusAmount,
+ UsedAt: u.UsedAt,
+ User: UserFromServiceShallow(u.User),
+ }
+}
diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go
index ad583ad0..a9b010b9 100644
--- a/backend/internal/handler/dto/types.go
+++ b/backend/internal/handler/dto/types.go
@@ -250,3 +250,28 @@ type BulkAssignResult struct {
Subscriptions []UserSubscription `json:"subscriptions"`
Errors []string `json:"errors"`
}
+
+// PromoCode 注册优惠码
+type PromoCode struct {
+ ID int64 `json:"id"`
+ Code string `json:"code"`
+ BonusAmount float64 `json:"bonus_amount"`
+ MaxUses int `json:"max_uses"`
+ UsedCount int `json:"used_count"`
+ Status string `json:"status"`
+ ExpiresAt *time.Time `json:"expires_at"`
+ Notes string `json:"notes"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// PromoCodeUsage 优惠码使用记录
+type PromoCodeUsage struct {
+ ID int64 `json:"id"`
+ PromoCodeID int64 `json:"promo_code_id"`
+ UserID int64 `json:"user_id"`
+ BonusAmount float64 `json:"bonus_amount"`
+ UsedAt time.Time `json:"used_at"`
+
+ User *User `json:"user,omitempty"`
+}
diff --git a/backend/internal/handler/handler.go b/backend/internal/handler/handler.go
index 817b71d3..047703f3 100644
--- a/backend/internal/handler/handler.go
+++ b/backend/internal/handler/handler.go
@@ -16,6 +16,7 @@ type AdminHandlers struct {
AntigravityOAuth *admin.AntigravityOAuthHandler
Proxy *admin.ProxyHandler
Redeem *admin.RedeemHandler
+ Promo *admin.PromoHandler
Setting *admin.SettingHandler
System *admin.SystemHandler
Subscription *admin.SubscriptionHandler
diff --git a/backend/internal/handler/wire.go b/backend/internal/handler/wire.go
index 1695f8a9..a5e62d0a 100644
--- a/backend/internal/handler/wire.go
+++ b/backend/internal/handler/wire.go
@@ -19,6 +19,7 @@ func ProvideAdminHandlers(
antigravityOAuthHandler *admin.AntigravityOAuthHandler,
proxyHandler *admin.ProxyHandler,
redeemHandler *admin.RedeemHandler,
+ promoHandler *admin.PromoHandler,
settingHandler *admin.SettingHandler,
systemHandler *admin.SystemHandler,
subscriptionHandler *admin.SubscriptionHandler,
@@ -36,6 +37,7 @@ func ProvideAdminHandlers(
AntigravityOAuth: antigravityOAuthHandler,
Proxy: proxyHandler,
Redeem: redeemHandler,
+ Promo: promoHandler,
Setting: settingHandler,
System: systemHandler,
Subscription: subscriptionHandler,
@@ -105,6 +107,7 @@ var ProviderSet = wire.NewSet(
admin.NewAntigravityOAuthHandler,
admin.NewProxyHandler,
admin.NewRedeemHandler,
+ admin.NewPromoHandler,
admin.NewSettingHandler,
ProvideSystemHandler,
admin.NewSubscriptionHandler,
diff --git a/backend/internal/middleware/rate_limiter.go b/backend/internal/middleware/rate_limiter.go
new file mode 100644
index 00000000..9526f071
--- /dev/null
+++ b/backend/internal/middleware/rate_limiter.go
@@ -0,0 +1,60 @@
+package middleware
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/redis/go-redis/v9"
+)
+
+// RateLimiter Redis 速率限制器
+type RateLimiter struct {
+ redis *redis.Client
+ prefix string
+}
+
+// NewRateLimiter 创建速率限制器实例
+func NewRateLimiter(redisClient *redis.Client) *RateLimiter {
+ return &RateLimiter{
+ redis: redisClient,
+ prefix: "rate_limit:",
+ }
+}
+
+// Limit 返回速率限制中间件
+// key: 限制类型标识
+// limit: 时间窗口内最大请求数
+// window: 时间窗口
+func (r *RateLimiter) Limit(key string, limit int, window time.Duration) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ ip := c.ClientIP()
+ redisKey := r.prefix + key + ":" + ip
+
+ ctx := c.Request.Context()
+
+ // 使用 INCR 原子操作增加计数
+ count, err := r.redis.Incr(ctx, redisKey).Result()
+ if err != nil {
+ // Redis 错误时放行,避免影响正常服务
+ c.Next()
+ return
+ }
+
+ // 首次访问时设置过期时间
+ if count == 1 {
+ r.redis.Expire(ctx, redisKey, window)
+ }
+
+ // 超过限制
+ if count > int64(limit) {
+ c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
+ "error": "rate limit exceeded",
+ "message": "Too many requests, please try again later",
+ })
+ return
+ }
+
+ c.Next()
+ }
+}
diff --git a/backend/internal/pkg/ctxkey/ctxkey.go b/backend/internal/pkg/ctxkey/ctxkey.go
index 3add78de..bd10eae0 100644
--- a/backend/internal/pkg/ctxkey/ctxkey.go
+++ b/backend/internal/pkg/ctxkey/ctxkey.go
@@ -9,4 +9,6 @@ const (
ForcePlatform Key = "ctx_force_platform"
// IsClaudeCodeClient 是否为 Claude Code 客户端,由中间件设置
IsClaudeCodeClient Key = "ctx_is_claude_code_client"
+ // Group 认证后的分组信息,由 API Key 认证中间件设置
+ Group Key = "ctx_group"
)
diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go
index 02f7b9a5..04ca7052 100644
--- a/backend/internal/repository/account_repo.go
+++ b/backend/internal/repository/account_repo.go
@@ -675,6 +675,40 @@ func (r *accountRepository) SetRateLimited(ctx context.Context, id int64, resetA
return err
}
+func (r *accountRepository) SetAntigravityQuotaScopeLimit(ctx context.Context, id int64, scope service.AntigravityQuotaScope, resetAt time.Time) error {
+ now := time.Now().UTC()
+ payload := map[string]string{
+ "rate_limited_at": now.Format(time.RFC3339),
+ "rate_limit_reset_at": resetAt.UTC().Format(time.RFC3339),
+ }
+ raw, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+
+ path := "{antigravity_quota_scopes," + string(scope) + "}"
+ client := clientFromContext(ctx, r.client)
+ result, err := client.ExecContext(
+ ctx,
+ "UPDATE accounts SET extra = jsonb_set(COALESCE(extra, '{}'::jsonb), $1::text[], $2::jsonb, true), updated_at = NOW() WHERE id = $3 AND deleted_at IS NULL",
+ path,
+ raw,
+ id,
+ )
+ if err != nil {
+ return err
+ }
+
+ affected, err := result.RowsAffected()
+ if err != nil {
+ return err
+ }
+ if affected == 0 {
+ return service.ErrAccountNotFound
+ }
+ return nil
+}
+
func (r *accountRepository) SetOverloaded(ctx context.Context, id int64, until time.Time) error {
_, err := r.client.Account.Update().
Where(dbaccount.IDEQ(id)).
@@ -718,6 +752,27 @@ func (r *accountRepository) ClearRateLimit(ctx context.Context, id int64) error
return err
}
+func (r *accountRepository) ClearAntigravityQuotaScopes(ctx context.Context, id int64) error {
+ client := clientFromContext(ctx, r.client)
+ result, err := client.ExecContext(
+ ctx,
+ "UPDATE accounts SET extra = COALESCE(extra, '{}'::jsonb) - 'antigravity_quota_scopes', updated_at = NOW() WHERE id = $1 AND deleted_at IS NULL",
+ id,
+ )
+ if err != nil {
+ return err
+ }
+
+ affected, err := result.RowsAffected()
+ if err != nil {
+ return err
+ }
+ if affected == 0 {
+ return service.ErrAccountNotFound
+ }
+ return nil
+}
+
func (r *accountRepository) UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error {
builder := r.client.Account.Update().
Where(dbaccount.IDEQ(id)).
diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go
index 6da551da..6b8cd40d 100644
--- a/backend/internal/repository/api_key_repo.go
+++ b/backend/internal/repository/api_key_repo.go
@@ -339,6 +339,7 @@ func groupEntityToService(g *dbent.Group) *service.Group {
RateMultiplier: g.RateMultiplier,
IsExclusive: g.IsExclusive,
Status: g.Status,
+ Hydrated: true,
SubscriptionType: g.SubscriptionType,
DailyLimitUSD: g.DailyLimitUsd,
WeeklyLimitUSD: g.WeeklyLimitUsd,
diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go
index a54f3116..63651dd7 100644
--- a/backend/internal/repository/group_repo.go
+++ b/backend/internal/repository/group_repo.go
@@ -60,6 +60,17 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
}
func (r *groupRepository) GetByID(ctx context.Context, id int64) (*service.Group, error) {
+ out, err := r.GetByIDLite(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+ count, _ := r.GetAccountCount(ctx, out.ID)
+ out.AccountCount = count
+ return out, nil
+}
+
+func (r *groupRepository) GetByIDLite(ctx context.Context, id int64) (*service.Group, error) {
+ // AccountCount is intentionally not loaded here; use GetByID when needed.
m, err := r.client.Group.Query().
Where(group.IDEQ(id)).
Only(ctx)
@@ -67,10 +78,7 @@ func (r *groupRepository) GetByID(ctx context.Context, id int64) (*service.Group
return nil, translatePersistenceError(err, service.ErrGroupNotFound, nil)
}
- out := groupEntityToService(m)
- count, _ := r.GetAccountCount(ctx, out.ID)
- out.AccountCount = count
- return out, nil
+ return groupEntityToService(m), nil
}
func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) error {
diff --git a/backend/internal/repository/group_repo_integration_test.go b/backend/internal/repository/group_repo_integration_test.go
index 660618a6..c31a9ec4 100644
--- a/backend/internal/repository/group_repo_integration_test.go
+++ b/backend/internal/repository/group_repo_integration_test.go
@@ -4,6 +4,8 @@ package repository
import (
"context"
+ "database/sql"
+ "errors"
"testing"
dbent "github.com/Wei-Shaw/sub2api/ent"
@@ -19,6 +21,20 @@ type GroupRepoSuite struct {
repo *groupRepository
}
+type forbidSQLExecutor struct {
+ called bool
+}
+
+func (s *forbidSQLExecutor) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
+ s.called = true
+ return nil, errors.New("unexpected sql exec")
+}
+
+func (s *forbidSQLExecutor) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
+ s.called = true
+ return nil, errors.New("unexpected sql query")
+}
+
func (s *GroupRepoSuite) SetupTest() {
s.ctx = context.Background()
tx := testEntTx(s.T())
@@ -57,6 +73,26 @@ func (s *GroupRepoSuite) TestGetByID_NotFound() {
s.Require().ErrorIs(err, service.ErrGroupNotFound)
}
+func (s *GroupRepoSuite) TestGetByIDLite_DoesNotUseAccountCount() {
+ group := &service.Group{
+ Name: "lite-group",
+ Platform: service.PlatformAnthropic,
+ RateMultiplier: 1.0,
+ IsExclusive: false,
+ Status: service.StatusActive,
+ SubscriptionType: service.SubscriptionTypeStandard,
+ }
+ s.Require().NoError(s.repo.Create(s.ctx, group))
+
+ spy := &forbidSQLExecutor{}
+ repo := newGroupRepositoryWithSQL(s.tx.Client(), spy)
+
+ got, err := repo.GetByIDLite(s.ctx, group.ID)
+ s.Require().NoError(err)
+ s.Require().Equal(group.ID, got.ID)
+ s.Require().False(spy.called, "expected no direct sql executor usage")
+}
+
func (s *GroupRepoSuite) TestUpdate() {
group := &service.Group{
Name: "original",
diff --git a/backend/internal/repository/promo_code_repo.go b/backend/internal/repository/promo_code_repo.go
new file mode 100644
index 00000000..98b422e0
--- /dev/null
+++ b/backend/internal/repository/promo_code_repo.go
@@ -0,0 +1,273 @@
+package repository
+
+import (
+ "context"
+
+ dbent "github.com/Wei-Shaw/sub2api/ent"
+ "github.com/Wei-Shaw/sub2api/ent/promocode"
+ "github.com/Wei-Shaw/sub2api/ent/promocodeusage"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+type promoCodeRepository struct {
+ client *dbent.Client
+}
+
+func NewPromoCodeRepository(client *dbent.Client) service.PromoCodeRepository {
+ return &promoCodeRepository{client: client}
+}
+
+func (r *promoCodeRepository) Create(ctx context.Context, code *service.PromoCode) error {
+ client := clientFromContext(ctx, r.client)
+ builder := client.PromoCode.Create().
+ SetCode(code.Code).
+ SetBonusAmount(code.BonusAmount).
+ SetMaxUses(code.MaxUses).
+ SetUsedCount(code.UsedCount).
+ SetStatus(code.Status).
+ SetNotes(code.Notes)
+
+ if code.ExpiresAt != nil {
+ builder.SetExpiresAt(*code.ExpiresAt)
+ }
+
+ created, err := builder.Save(ctx)
+ if err != nil {
+ return err
+ }
+
+ code.ID = created.ID
+ code.CreatedAt = created.CreatedAt
+ code.UpdatedAt = created.UpdatedAt
+ return nil
+}
+
+func (r *promoCodeRepository) GetByID(ctx context.Context, id int64) (*service.PromoCode, error) {
+ m, err := r.client.PromoCode.Query().
+ Where(promocode.IDEQ(id)).
+ Only(ctx)
+ if err != nil {
+ if dbent.IsNotFound(err) {
+ return nil, service.ErrPromoCodeNotFound
+ }
+ return nil, err
+ }
+ return promoCodeEntityToService(m), nil
+}
+
+func (r *promoCodeRepository) GetByCode(ctx context.Context, code string) (*service.PromoCode, error) {
+ m, err := r.client.PromoCode.Query().
+ Where(promocode.CodeEqualFold(code)).
+ Only(ctx)
+ if err != nil {
+ if dbent.IsNotFound(err) {
+ return nil, service.ErrPromoCodeNotFound
+ }
+ return nil, err
+ }
+ return promoCodeEntityToService(m), nil
+}
+
+func (r *promoCodeRepository) GetByCodeForUpdate(ctx context.Context, code string) (*service.PromoCode, error) {
+ client := clientFromContext(ctx, r.client)
+ m, err := client.PromoCode.Query().
+ Where(promocode.CodeEqualFold(code)).
+ ForUpdate().
+ Only(ctx)
+ if err != nil {
+ if dbent.IsNotFound(err) {
+ return nil, service.ErrPromoCodeNotFound
+ }
+ return nil, err
+ }
+ return promoCodeEntityToService(m), nil
+}
+
+func (r *promoCodeRepository) Update(ctx context.Context, code *service.PromoCode) error {
+ client := clientFromContext(ctx, r.client)
+ builder := client.PromoCode.UpdateOneID(code.ID).
+ SetCode(code.Code).
+ SetBonusAmount(code.BonusAmount).
+ SetMaxUses(code.MaxUses).
+ SetUsedCount(code.UsedCount).
+ SetStatus(code.Status).
+ SetNotes(code.Notes)
+
+ if code.ExpiresAt != nil {
+ builder.SetExpiresAt(*code.ExpiresAt)
+ } else {
+ builder.ClearExpiresAt()
+ }
+
+ updated, err := builder.Save(ctx)
+ if err != nil {
+ if dbent.IsNotFound(err) {
+ return service.ErrPromoCodeNotFound
+ }
+ return err
+ }
+
+ code.UpdatedAt = updated.UpdatedAt
+ return nil
+}
+
+func (r *promoCodeRepository) Delete(ctx context.Context, id int64) error {
+ client := clientFromContext(ctx, r.client)
+ _, err := client.PromoCode.Delete().Where(promocode.IDEQ(id)).Exec(ctx)
+ return err
+}
+
+func (r *promoCodeRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.PromoCode, *pagination.PaginationResult, error) {
+ return r.ListWithFilters(ctx, params, "", "")
+}
+
+func (r *promoCodeRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, status, search string) ([]service.PromoCode, *pagination.PaginationResult, error) {
+ q := r.client.PromoCode.Query()
+
+ if status != "" {
+ q = q.Where(promocode.StatusEQ(status))
+ }
+ if search != "" {
+ q = q.Where(promocode.CodeContainsFold(search))
+ }
+
+ total, err := q.Count(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ codes, err := q.
+ Offset(params.Offset()).
+ Limit(params.Limit()).
+ Order(dbent.Desc(promocode.FieldID)).
+ All(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ outCodes := promoCodeEntitiesToService(codes)
+
+ return outCodes, paginationResultFromTotal(int64(total), params), nil
+}
+
+func (r *promoCodeRepository) CreateUsage(ctx context.Context, usage *service.PromoCodeUsage) error {
+ client := clientFromContext(ctx, r.client)
+ created, err := client.PromoCodeUsage.Create().
+ SetPromoCodeID(usage.PromoCodeID).
+ SetUserID(usage.UserID).
+ SetBonusAmount(usage.BonusAmount).
+ SetUsedAt(usage.UsedAt).
+ Save(ctx)
+ if err != nil {
+ return err
+ }
+
+ usage.ID = created.ID
+ return nil
+}
+
+func (r *promoCodeRepository) GetUsageByPromoCodeAndUser(ctx context.Context, promoCodeID, userID int64) (*service.PromoCodeUsage, error) {
+ m, err := r.client.PromoCodeUsage.Query().
+ Where(
+ promocodeusage.PromoCodeIDEQ(promoCodeID),
+ promocodeusage.UserIDEQ(userID),
+ ).
+ Only(ctx)
+ if err != nil {
+ if dbent.IsNotFound(err) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return promoCodeUsageEntityToService(m), nil
+}
+
+func (r *promoCodeRepository) ListUsagesByPromoCode(ctx context.Context, promoCodeID int64, params pagination.PaginationParams) ([]service.PromoCodeUsage, *pagination.PaginationResult, error) {
+ q := r.client.PromoCodeUsage.Query().
+ Where(promocodeusage.PromoCodeIDEQ(promoCodeID))
+
+ total, err := q.Count(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ usages, err := q.
+ WithUser().
+ Offset(params.Offset()).
+ Limit(params.Limit()).
+ Order(dbent.Desc(promocodeusage.FieldID)).
+ All(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ outUsages := promoCodeUsageEntitiesToService(usages)
+
+ return outUsages, paginationResultFromTotal(int64(total), params), nil
+}
+
+func (r *promoCodeRepository) IncrementUsedCount(ctx context.Context, id int64) error {
+ client := clientFromContext(ctx, r.client)
+ _, err := client.PromoCode.UpdateOneID(id).
+ AddUsedCount(1).
+ Save(ctx)
+ return err
+}
+
+// Entity to Service conversions
+
+func promoCodeEntityToService(m *dbent.PromoCode) *service.PromoCode {
+ if m == nil {
+ return nil
+ }
+ return &service.PromoCode{
+ ID: m.ID,
+ Code: m.Code,
+ BonusAmount: m.BonusAmount,
+ MaxUses: m.MaxUses,
+ UsedCount: m.UsedCount,
+ Status: m.Status,
+ ExpiresAt: m.ExpiresAt,
+ Notes: derefString(m.Notes),
+ CreatedAt: m.CreatedAt,
+ UpdatedAt: m.UpdatedAt,
+ }
+}
+
+func promoCodeEntitiesToService(models []*dbent.PromoCode) []service.PromoCode {
+ out := make([]service.PromoCode, 0, len(models))
+ for i := range models {
+ if s := promoCodeEntityToService(models[i]); s != nil {
+ out = append(out, *s)
+ }
+ }
+ return out
+}
+
+func promoCodeUsageEntityToService(m *dbent.PromoCodeUsage) *service.PromoCodeUsage {
+ if m == nil {
+ return nil
+ }
+ out := &service.PromoCodeUsage{
+ ID: m.ID,
+ PromoCodeID: m.PromoCodeID,
+ UserID: m.UserID,
+ BonusAmount: m.BonusAmount,
+ UsedAt: m.UsedAt,
+ }
+ if m.Edges.User != nil {
+ out.User = userEntityToService(m.Edges.User)
+ }
+ return out
+}
+
+func promoCodeUsageEntitiesToService(models []*dbent.PromoCodeUsage) []service.PromoCodeUsage {
+ out := make([]service.PromoCodeUsage, 0, len(models))
+ for i := range models {
+ if s := promoCodeUsageEntityToService(models[i]); s != nil {
+ out = append(out, *s)
+ }
+ }
+ return out
+}
diff --git a/backend/internal/repository/wire.go b/backend/internal/repository/wire.go
index ba09f85e..0a6118e2 100644
--- a/backend/internal/repository/wire.go
+++ b/backend/internal/repository/wire.go
@@ -45,6 +45,7 @@ var ProviderSet = wire.NewSet(
NewAccountRepository,
NewProxyRepository,
NewRedeemCodeRepository,
+ NewPromoCodeRepository,
NewUsageLogRepository,
NewSettingRepository,
NewUserSubscriptionRepository,
diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go
index 6e52c5bc..41d8bfdb 100644
--- a/backend/internal/server/api_contract_test.go
+++ b/backend/internal/server/api_contract_test.go
@@ -398,7 +398,7 @@ func newContractDeps(t *testing.T) *contractDeps {
settingRepo := newStubSettingRepo()
settingService := service.NewSettingService(settingRepo, cfg)
- authHandler := handler.NewAuthHandler(cfg, nil, userService, settingService)
+ authHandler := handler.NewAuthHandler(cfg, nil, userService, settingService, nil)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil, nil)
@@ -575,6 +575,10 @@ func (stubGroupRepo) GetByID(ctx context.Context, id int64) (*service.Group, err
return nil, service.ErrGroupNotFound
}
+func (stubGroupRepo) GetByIDLite(ctx context.Context, id int64) (*service.Group, error) {
+ return nil, service.ErrGroupNotFound
+}
+
func (stubGroupRepo) Update(ctx context.Context, group *service.Group) error {
return errors.New("not implemented")
}
diff --git a/backend/internal/server/http.go b/backend/internal/server/http.go
index 9fb77c61..a7d1d3b5 100644
--- a/backend/internal/server/http.go
+++ b/backend/internal/server/http.go
@@ -13,6 +13,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/wire"
+ "github.com/redis/go-redis/v9"
)
// ProviderSet 提供服务器层的依赖
@@ -31,6 +32,7 @@ func ProvideRouter(
apiKeyService *service.APIKeyService,
subscriptionService *service.SubscriptionService,
settingService *service.SettingService,
+ redisClient *redis.Client,
) *gin.Engine {
if cfg.Server.Mode == "release" {
gin.SetMode(gin.ReleaseMode)
@@ -48,7 +50,7 @@ func ProvideRouter(
}
}
- return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, settingService, cfg)
+ return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, settingService, cfg, redisClient)
}
// ProvideHTTPServer 提供 HTTP 服务器
diff --git a/backend/internal/server/middleware/api_key_auth.go b/backend/internal/server/middleware/api_key_auth.go
index d93724f2..dff6ba95 100644
--- a/backend/internal/server/middleware/api_key_auth.go
+++ b/backend/internal/server/middleware/api_key_auth.go
@@ -1,11 +1,13 @@
package middleware
import (
+ "context"
"errors"
"log"
"strings"
"github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
"github.com/Wei-Shaw/sub2api/internal/service"
@@ -103,6 +105,7 @@ func apiKeyAuthWithSubscription(apiKeyService *service.APIKeyService, subscripti
Concurrency: apiKey.User.Concurrency,
})
c.Set(string(ContextKeyUserRole), apiKey.User.Role)
+ setGroupContext(c, apiKey.Group)
c.Next()
return
}
@@ -161,6 +164,7 @@ func apiKeyAuthWithSubscription(apiKeyService *service.APIKeyService, subscripti
Concurrency: apiKey.User.Concurrency,
})
c.Set(string(ContextKeyUserRole), apiKey.User.Role)
+ setGroupContext(c, apiKey.Group)
c.Next()
}
@@ -185,3 +189,14 @@ func GetSubscriptionFromContext(c *gin.Context) (*service.UserSubscription, bool
subscription, ok := value.(*service.UserSubscription)
return subscription, ok
}
+
+func setGroupContext(c *gin.Context, group *service.Group) {
+ if !service.IsGroupContextValid(group) {
+ return
+ }
+ if existing, ok := c.Request.Context().Value(ctxkey.Group).(*service.Group); ok && existing != nil && existing.ID == group.ID && service.IsGroupContextValid(existing) {
+ return
+ }
+ ctx := context.WithValue(c.Request.Context(), ctxkey.Group, group)
+ c.Request = c.Request.WithContext(ctx)
+}
diff --git a/backend/internal/server/middleware/api_key_auth_google.go b/backend/internal/server/middleware/api_key_auth_google.go
index c5afd7ef..1a0b0dd5 100644
--- a/backend/internal/server/middleware/api_key_auth_google.go
+++ b/backend/internal/server/middleware/api_key_auth_google.go
@@ -63,6 +63,7 @@ func APIKeyAuthWithSubscriptionGoogle(apiKeyService *service.APIKeyService, subs
Concurrency: apiKey.User.Concurrency,
})
c.Set(string(ContextKeyUserRole), apiKey.User.Role)
+ setGroupContext(c, apiKey.Group)
c.Next()
return
}
@@ -102,6 +103,7 @@ func APIKeyAuthWithSubscriptionGoogle(apiKeyService *service.APIKeyService, subs
Concurrency: apiKey.User.Concurrency,
})
c.Set(string(ContextKeyUserRole), apiKey.User.Role)
+ setGroupContext(c, apiKey.Group)
c.Next()
}
}
diff --git a/backend/internal/server/middleware/api_key_auth_google_test.go b/backend/internal/server/middleware/api_key_auth_google_test.go
index 0ed5a4a2..07b8e370 100644
--- a/backend/internal/server/middleware/api_key_auth_google_test.go
+++ b/backend/internal/server/middleware/api_key_auth_google_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
@@ -133,6 +134,70 @@ func TestApiKeyAuthWithSubscriptionGoogle_QueryApiKeyRejected(t *testing.T) {
require.Equal(t, "INVALID_ARGUMENT", resp.Error.Status)
}
+func TestApiKeyAuthWithSubscriptionGoogleSetsGroupContext(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+
+ group := &service.Group{
+ ID: 99,
+ Name: "g1",
+ Status: service.StatusActive,
+ Platform: service.PlatformGemini,
+ Hydrated: true,
+ }
+ user := &service.User{
+ ID: 7,
+ Role: service.RoleUser,
+ Status: service.StatusActive,
+ Balance: 10,
+ Concurrency: 3,
+ }
+ apiKey := &service.APIKey{
+ ID: 100,
+ UserID: user.ID,
+ Key: "test-key",
+ Status: service.StatusActive,
+ User: user,
+ Group: group,
+ }
+ apiKey.GroupID = &group.ID
+
+ apiKeyService := service.NewAPIKeyService(
+ fakeAPIKeyRepo{
+ getByKey: func(ctx context.Context, key string) (*service.APIKey, error) {
+ if key != apiKey.Key {
+ return nil, service.ErrAPIKeyNotFound
+ }
+ clone := *apiKey
+ return &clone, nil
+ },
+ },
+ nil,
+ nil,
+ nil,
+ nil,
+ &config.Config{RunMode: config.RunModeSimple},
+ )
+
+ cfg := &config.Config{RunMode: config.RunModeSimple}
+ r := gin.New()
+ r.Use(APIKeyAuthWithSubscriptionGoogle(apiKeyService, nil, cfg))
+ r.GET("/v1beta/test", func(c *gin.Context) {
+ groupFromCtx, ok := c.Request.Context().Value(ctxkey.Group).(*service.Group)
+ if !ok || groupFromCtx == nil || groupFromCtx.ID != group.ID {
+ c.JSON(http.StatusInternalServerError, gin.H{"ok": false})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"ok": true})
+ })
+
+ req := httptest.NewRequest(http.MethodGet, "/v1beta/test", nil)
+ req.Header.Set("x-api-key", apiKey.Key)
+ rec := httptest.NewRecorder()
+ r.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code)
+}
+
func TestApiKeyAuthWithSubscriptionGoogle_QueryKeyAllowedOnV1Beta(t *testing.T) {
gin.SetMode(gin.TestMode)
diff --git a/backend/internal/server/middleware/api_key_auth_test.go b/backend/internal/server/middleware/api_key_auth_test.go
index d50fb7b2..182ea5f8 100644
--- a/backend/internal/server/middleware/api_key_auth_test.go
+++ b/backend/internal/server/middleware/api_key_auth_test.go
@@ -11,6 +11,7 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
@@ -25,6 +26,7 @@ func TestSimpleModeBypassesQuotaCheck(t *testing.T) {
ID: 42,
Name: "sub",
Status: service.StatusActive,
+ Hydrated: true,
SubscriptionType: service.SubscriptionTypeSubscription,
DailyLimitUSD: &limit,
}
@@ -110,6 +112,129 @@ func TestSimpleModeBypassesQuotaCheck(t *testing.T) {
})
}
+func TestAPIKeyAuthSetsGroupContext(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+
+ group := &service.Group{
+ ID: 101,
+ Name: "g1",
+ Status: service.StatusActive,
+ Platform: service.PlatformAnthropic,
+ Hydrated: true,
+ }
+ user := &service.User{
+ ID: 7,
+ Role: service.RoleUser,
+ Status: service.StatusActive,
+ Balance: 10,
+ Concurrency: 3,
+ }
+ apiKey := &service.APIKey{
+ ID: 100,
+ UserID: user.ID,
+ Key: "test-key",
+ Status: service.StatusActive,
+ User: user,
+ Group: group,
+ }
+ apiKey.GroupID = &group.ID
+
+ apiKeyRepo := &stubApiKeyRepo{
+ getByKey: func(ctx context.Context, key string) (*service.APIKey, error) {
+ if key != apiKey.Key {
+ return nil, service.ErrAPIKeyNotFound
+ }
+ clone := *apiKey
+ return &clone, nil
+ },
+ }
+
+ cfg := &config.Config{RunMode: config.RunModeSimple}
+ apiKeyService := service.NewAPIKeyService(apiKeyRepo, nil, nil, nil, nil, cfg)
+ router := gin.New()
+ router.Use(gin.HandlerFunc(NewAPIKeyAuthMiddleware(apiKeyService, nil, cfg)))
+ router.GET("/t", func(c *gin.Context) {
+ groupFromCtx, ok := c.Request.Context().Value(ctxkey.Group).(*service.Group)
+ if !ok || groupFromCtx == nil || groupFromCtx.ID != group.ID {
+ c.JSON(http.StatusInternalServerError, gin.H{"ok": false})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"ok": true})
+ })
+
+ w := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/t", nil)
+ req.Header.Set("x-api-key", apiKey.Key)
+ router.ServeHTTP(w, req)
+
+ require.Equal(t, http.StatusOK, w.Code)
+}
+
+func TestAPIKeyAuthOverwritesInvalidContextGroup(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+
+ group := &service.Group{
+ ID: 101,
+ Name: "g1",
+ Status: service.StatusActive,
+ Platform: service.PlatformAnthropic,
+ Hydrated: true,
+ }
+ user := &service.User{
+ ID: 7,
+ Role: service.RoleUser,
+ Status: service.StatusActive,
+ Balance: 10,
+ Concurrency: 3,
+ }
+ apiKey := &service.APIKey{
+ ID: 100,
+ UserID: user.ID,
+ Key: "test-key",
+ Status: service.StatusActive,
+ User: user,
+ Group: group,
+ }
+ apiKey.GroupID = &group.ID
+
+ apiKeyRepo := &stubApiKeyRepo{
+ getByKey: func(ctx context.Context, key string) (*service.APIKey, error) {
+ if key != apiKey.Key {
+ return nil, service.ErrAPIKeyNotFound
+ }
+ clone := *apiKey
+ return &clone, nil
+ },
+ }
+
+ cfg := &config.Config{RunMode: config.RunModeSimple}
+ apiKeyService := service.NewAPIKeyService(apiKeyRepo, nil, nil, nil, nil, cfg)
+ router := gin.New()
+ router.Use(gin.HandlerFunc(NewAPIKeyAuthMiddleware(apiKeyService, nil, cfg)))
+
+ invalidGroup := &service.Group{
+ ID: group.ID,
+ Platform: group.Platform,
+ Status: group.Status,
+ }
+ router.GET("/t", func(c *gin.Context) {
+ groupFromCtx, ok := c.Request.Context().Value(ctxkey.Group).(*service.Group)
+ if !ok || groupFromCtx == nil || groupFromCtx.ID != group.ID || !groupFromCtx.Hydrated || groupFromCtx == invalidGroup {
+ c.JSON(http.StatusInternalServerError, gin.H{"ok": false})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"ok": true})
+ })
+
+ w := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/t", nil)
+ req.Header.Set("x-api-key", apiKey.Key)
+ req = req.WithContext(context.WithValue(req.Context(), ctxkey.Group, invalidGroup))
+ router.ServeHTTP(w, req)
+
+ require.Equal(t, http.StatusOK, w.Code)
+}
+
func newAuthTestRouter(apiKeyService *service.APIKeyService, subscriptionService *service.SubscriptionService, cfg *config.Config) *gin.Engine {
router := gin.New()
router.Use(gin.HandlerFunc(NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, cfg)))
diff --git a/backend/internal/server/router.go b/backend/internal/server/router.go
index f5e2e657..70f7da84 100644
--- a/backend/internal/server/router.go
+++ b/backend/internal/server/router.go
@@ -11,6 +11,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/web"
"github.com/gin-gonic/gin"
+ "github.com/redis/go-redis/v9"
)
// SetupRouter 配置路由器中间件和路由
@@ -24,6 +25,7 @@ func SetupRouter(
subscriptionService *service.SubscriptionService,
settingService *service.SettingService,
cfg *config.Config,
+ redisClient *redis.Client,
) *gin.Engine {
// 应用中间件
r.Use(middleware2.Logger())
@@ -44,7 +46,7 @@ func SetupRouter(
}
// 注册路由
- registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, cfg)
+ registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, cfg, redisClient)
return r
}
@@ -59,6 +61,7 @@ func registerRoutes(
apiKeyService *service.APIKeyService,
subscriptionService *service.SubscriptionService,
cfg *config.Config,
+ redisClient *redis.Client,
) {
// 通用路由(健康检查、状态等)
routes.RegisterCommonRoutes(r)
@@ -67,7 +70,7 @@ func registerRoutes(
v1 := r.Group("/api/v1")
// 注册各模块路由
- routes.RegisterAuthRoutes(v1, h, jwtAuth)
+ routes.RegisterAuthRoutes(v1, h, jwtAuth, redisClient)
routes.RegisterUserRoutes(v1, h, jwtAuth)
routes.RegisterAdminRoutes(v1, h, adminAuth)
routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, cfg)
diff --git a/backend/internal/server/routes/admin.go b/backend/internal/server/routes/admin.go
index 663c2d02..6f40c491 100644
--- a/backend/internal/server/routes/admin.go
+++ b/backend/internal/server/routes/admin.go
@@ -44,6 +44,9 @@ func RegisterAdminRoutes(
// 卡密管理
registerRedeemCodeRoutes(admin, h)
+ // 优惠码管理
+ registerPromoCodeRoutes(admin, h)
+
// 系统设置
registerSettingsRoutes(admin, h)
@@ -201,6 +204,18 @@ func registerRedeemCodeRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
}
}
+func registerPromoCodeRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
+ promoCodes := admin.Group("/promo-codes")
+ {
+ promoCodes.GET("", h.Admin.Promo.List)
+ promoCodes.GET("/:id", h.Admin.Promo.GetByID)
+ promoCodes.POST("", h.Admin.Promo.Create)
+ promoCodes.PUT("/:id", h.Admin.Promo.Update)
+ promoCodes.DELETE("/:id", h.Admin.Promo.Delete)
+ promoCodes.GET("/:id/usages", h.Admin.Promo.GetUsages)
+ }
+}
+
func registerSettingsRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
adminSettings := admin.Group("/settings")
{
diff --git a/backend/internal/server/routes/auth.go b/backend/internal/server/routes/auth.go
index e61d3939..7d8a79e9 100644
--- a/backend/internal/server/routes/auth.go
+++ b/backend/internal/server/routes/auth.go
@@ -1,24 +1,34 @@
package routes
import (
+ "time"
+
"github.com/Wei-Shaw/sub2api/internal/handler"
- "github.com/Wei-Shaw/sub2api/internal/server/middleware"
+ "github.com/Wei-Shaw/sub2api/internal/middleware"
+ servermiddleware "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/gin-gonic/gin"
+ "github.com/redis/go-redis/v9"
)
// RegisterAuthRoutes 注册认证相关路由
func RegisterAuthRoutes(
v1 *gin.RouterGroup,
h *handler.Handlers,
- jwtAuth middleware.JWTAuthMiddleware,
+ jwtAuth servermiddleware.JWTAuthMiddleware,
+ redisClient *redis.Client,
) {
+ // 创建速率限制器
+ rateLimiter := middleware.NewRateLimiter(redisClient)
+
// 公开接口
auth := v1.Group("/auth")
{
auth.POST("/register", h.Auth.Register)
auth.POST("/login", h.Auth.Login)
auth.POST("/send-verify-code", h.Auth.SendVerifyCode)
+ // 优惠码验证接口添加速率限制:每分钟最多 10 次
+ auth.POST("/validate-promo-code", rateLimiter.Limit("validate-promo", 10, time.Minute), h.Auth.ValidatePromoCode)
auth.GET("/oauth/linuxdo/start", h.Auth.LinuxDoOAuthStart)
auth.GET("/oauth/linuxdo/callback", h.Auth.LinuxDoOAuthCallback)
}
diff --git a/backend/internal/service/account_service.go b/backend/internal/service/account_service.go
index 3407f33a..2f138b81 100644
--- a/backend/internal/service/account_service.go
+++ b/backend/internal/service/account_service.go
@@ -49,10 +49,12 @@ type AccountRepository interface {
ListSchedulableByGroupIDAndPlatforms(ctx context.Context, groupID int64, platforms []string) ([]Account, error)
SetRateLimited(ctx context.Context, id int64, resetAt time.Time) error
+ SetAntigravityQuotaScopeLimit(ctx context.Context, id int64, scope AntigravityQuotaScope, resetAt time.Time) error
SetOverloaded(ctx context.Context, id int64, until time.Time) error
SetTempUnschedulable(ctx context.Context, id int64, until time.Time, reason string) error
ClearTempUnschedulable(ctx context.Context, id int64) error
ClearRateLimit(ctx context.Context, id int64) error
+ ClearAntigravityQuotaScopes(ctx context.Context, id int64) error
UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error
UpdateExtra(ctx context.Context, id int64, updates map[string]any) error
BulkUpdate(ctx context.Context, ids []int64, updates AccountBulkUpdate) (int64, error)
diff --git a/backend/internal/service/account_service_delete_test.go b/backend/internal/service/account_service_delete_test.go
index edad8672..6923067d 100644
--- a/backend/internal/service/account_service_delete_test.go
+++ b/backend/internal/service/account_service_delete_test.go
@@ -139,6 +139,10 @@ func (s *accountRepoStub) SetRateLimited(ctx context.Context, id int64, resetAt
panic("unexpected SetRateLimited call")
}
+func (s *accountRepoStub) SetAntigravityQuotaScopeLimit(ctx context.Context, id int64, scope AntigravityQuotaScope, resetAt time.Time) error {
+ panic("unexpected SetAntigravityQuotaScopeLimit call")
+}
+
func (s *accountRepoStub) SetOverloaded(ctx context.Context, id int64, until time.Time) error {
panic("unexpected SetOverloaded call")
}
@@ -155,6 +159,10 @@ func (s *accountRepoStub) ClearRateLimit(ctx context.Context, id int64) error {
panic("unexpected ClearRateLimit call")
}
+func (s *accountRepoStub) ClearAntigravityQuotaScopes(ctx context.Context, id int64) error {
+ panic("unexpected ClearAntigravityQuotaScopes call")
+}
+
func (s *accountRepoStub) UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error {
panic("unexpected UpdateSessionWindow call")
}
diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go
index 4288381c..14bb6daf 100644
--- a/backend/internal/service/admin_service.go
+++ b/backend/internal/service/admin_service.go
@@ -576,18 +576,33 @@ func (s *adminServiceImpl) validateFallbackGroup(ctx context.Context, currentGro
return fmt.Errorf("cannot set self as fallback group")
}
- // 检查降级分组是否存在
- fallbackGroup, err := s.groupRepo.GetByID(ctx, fallbackGroupID)
- if err != nil {
- return fmt.Errorf("fallback group not found: %w", err)
- }
+ visited := map[int64]struct{}{}
+ nextID := fallbackGroupID
+ for {
+ if _, seen := visited[nextID]; seen {
+ return fmt.Errorf("fallback group cycle detected")
+ }
+ visited[nextID] = struct{}{}
+ if currentGroupID > 0 && nextID == currentGroupID {
+ return fmt.Errorf("fallback group cycle detected")
+ }
- // 降级分组不能启用 claude_code_only,否则会造成死循环
- if fallbackGroup.ClaudeCodeOnly {
- return fmt.Errorf("fallback group cannot have claude_code_only enabled")
- }
+ // 检查降级分组是否存在
+ fallbackGroup, err := s.groupRepo.GetByIDLite(ctx, nextID)
+ if err != nil {
+ return fmt.Errorf("fallback group not found: %w", err)
+ }
- return nil
+ // 降级分组不能启用 claude_code_only,否则会造成死循环
+ if nextID == fallbackGroupID && fallbackGroup.ClaudeCodeOnly {
+ return fmt.Errorf("fallback group cannot have claude_code_only enabled")
+ }
+
+ if fallbackGroup.FallbackGroupID == nil {
+ return nil
+ }
+ nextID = *fallbackGroup.FallbackGroupID
+ }
}
func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *UpdateGroupInput) (*Group, error) {
diff --git a/backend/internal/service/admin_service_delete_test.go b/backend/internal/service/admin_service_delete_test.go
index 351f64e8..31639472 100644
--- a/backend/internal/service/admin_service_delete_test.go
+++ b/backend/internal/service/admin_service_delete_test.go
@@ -107,6 +107,10 @@ func (s *groupRepoStub) GetByID(ctx context.Context, id int64) (*Group, error) {
panic("unexpected GetByID call")
}
+func (s *groupRepoStub) GetByIDLite(ctx context.Context, id int64) (*Group, error) {
+ panic("unexpected GetByIDLite call")
+}
+
func (s *groupRepoStub) Update(ctx context.Context, group *Group) error {
panic("unexpected Update call")
}
diff --git a/backend/internal/service/admin_service_group_test.go b/backend/internal/service/admin_service_group_test.go
index 26d6eedf..e0574e2e 100644
--- a/backend/internal/service/admin_service_group_test.go
+++ b/backend/internal/service/admin_service_group_test.go
@@ -45,6 +45,13 @@ func (s *groupRepoStubForAdmin) GetByID(_ context.Context, _ int64) (*Group, err
return s.getByID, nil
}
+func (s *groupRepoStubForAdmin) GetByIDLite(_ context.Context, _ int64) (*Group, error) {
+ if s.getErr != nil {
+ return nil, s.getErr
+ }
+ return s.getByID, nil
+}
+
func (s *groupRepoStubForAdmin) Delete(_ context.Context, _ int64) error {
panic("unexpected Delete call")
}
@@ -290,3 +297,84 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) {
require.True(t, *repo.listWithFiltersIsExclusive)
})
}
+
+func TestAdminService_ValidateFallbackGroup_DetectsCycle(t *testing.T) {
+ groupID := int64(1)
+ fallbackID := int64(2)
+ repo := &groupRepoStubForFallbackCycle{
+ groups: map[int64]*Group{
+ groupID: {
+ ID: groupID,
+ FallbackGroupID: &fallbackID,
+ },
+ fallbackID: {
+ ID: fallbackID,
+ FallbackGroupID: &groupID,
+ },
+ },
+ }
+ svc := &adminServiceImpl{groupRepo: repo}
+
+ err := svc.validateFallbackGroup(context.Background(), groupID, fallbackID)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "fallback group cycle")
+}
+
+type groupRepoStubForFallbackCycle struct {
+ groups map[int64]*Group
+}
+
+func (s *groupRepoStubForFallbackCycle) Create(_ context.Context, _ *Group) error {
+ panic("unexpected Create call")
+}
+
+func (s *groupRepoStubForFallbackCycle) Update(_ context.Context, _ *Group) error {
+ panic("unexpected Update call")
+}
+
+func (s *groupRepoStubForFallbackCycle) GetByID(ctx context.Context, id int64) (*Group, error) {
+ return s.GetByIDLite(ctx, id)
+}
+
+func (s *groupRepoStubForFallbackCycle) GetByIDLite(_ context.Context, id int64) (*Group, error) {
+ if g, ok := s.groups[id]; ok {
+ return g, nil
+ }
+ return nil, ErrGroupNotFound
+}
+
+func (s *groupRepoStubForFallbackCycle) Delete(_ context.Context, _ int64) error {
+ panic("unexpected Delete call")
+}
+
+func (s *groupRepoStubForFallbackCycle) DeleteCascade(_ context.Context, _ int64) ([]int64, error) {
+ panic("unexpected DeleteCascade call")
+}
+
+func (s *groupRepoStubForFallbackCycle) List(_ context.Context, _ pagination.PaginationParams) ([]Group, *pagination.PaginationResult, error) {
+ panic("unexpected List call")
+}
+
+func (s *groupRepoStubForFallbackCycle) ListWithFilters(_ context.Context, _ pagination.PaginationParams, _, _, _ string, _ *bool) ([]Group, *pagination.PaginationResult, error) {
+ panic("unexpected ListWithFilters call")
+}
+
+func (s *groupRepoStubForFallbackCycle) ListActive(_ context.Context) ([]Group, error) {
+ panic("unexpected ListActive call")
+}
+
+func (s *groupRepoStubForFallbackCycle) ListActiveByPlatform(_ context.Context, _ string) ([]Group, error) {
+ panic("unexpected ListActiveByPlatform call")
+}
+
+func (s *groupRepoStubForFallbackCycle) ExistsByName(_ context.Context, _ string) (bool, error) {
+ panic("unexpected ExistsByName call")
+}
+
+func (s *groupRepoStubForFallbackCycle) GetAccountCount(_ context.Context, _ int64) (int64, error) {
+ panic("unexpected GetAccountCount call")
+}
+
+func (s *groupRepoStubForFallbackCycle) DeleteAccountGroupsByGroupID(_ context.Context, _ int64) (int64, error) {
+ panic("unexpected DeleteAccountGroupsByGroupID call")
+}
diff --git a/backend/internal/service/antigravity_gateway_service.go b/backend/internal/service/antigravity_gateway_service.go
index 573017cd..4fd55757 100644
--- a/backend/internal/service/antigravity_gateway_service.go
+++ b/backend/internal/service/antigravity_gateway_service.go
@@ -93,6 +93,7 @@ var antigravityPrefixMapping = []struct {
// 长前缀优先
{"gemini-2.5-flash-image", "gemini-3-pro-image"}, // gemini-2.5-flash-image → 3-pro-image
{"gemini-3-pro-image", "gemini-3-pro-image"}, // gemini-3-pro-image-preview 等
+ {"gemini-3-flash", "gemini-3-flash"}, // gemini-3-flash-preview 等 → gemini-3-flash
{"claude-3-5-sonnet", "claude-sonnet-4-5"}, // 旧版 claude-3-5-sonnet-xxx
{"claude-sonnet-4-5", "claude-sonnet-4-5"}, // claude-sonnet-4-5-xxx
{"claude-haiku-4-5", "claude-sonnet-4-5"}, // claude-haiku-4-5-xxx → sonnet
@@ -502,6 +503,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
originalModel := claudeReq.Model
mappedModel := s.getMappedModel(account, claudeReq.Model)
+ quotaScope, _ := resolveAntigravityQuotaScope(originalModel)
// 获取 access_token
if s.tokenProvider == nil {
@@ -603,7 +605,7 @@ urlFallbackLoop:
}
// 所有重试都失败,标记限流状态
if resp.StatusCode == 429 {
- s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody)
+ s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody, quotaScope)
}
// 最后一次尝试也失败
resp = &http.Response{
@@ -696,7 +698,7 @@ urlFallbackLoop:
// 处理错误响应(重试后仍失败或不触发重试)
if resp.StatusCode >= 400 {
- s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody)
+ s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody, quotaScope)
if s.shouldFailoverUpstreamError(resp.StatusCode) {
return nil, &UpstreamFailoverError{StatusCode: resp.StatusCode}
@@ -1021,6 +1023,7 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co
if len(body) == 0 {
return nil, s.writeGoogleError(c, http.StatusBadRequest, "Request body is empty")
}
+ quotaScope, _ := resolveAntigravityQuotaScope(originalModel)
// 解析请求以获取 image_size(用于图片计费)
imageSize := s.extractImageSize(body)
@@ -1146,7 +1149,7 @@ urlFallbackLoop:
}
// 所有重试都失败,标记限流状态
if resp.StatusCode == 429 {
- s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody)
+ s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody, quotaScope)
}
resp = &http.Response{
StatusCode: resp.StatusCode,
@@ -1200,7 +1203,7 @@ urlFallbackLoop:
goto handleSuccess
}
- s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody)
+ s.handleUpstreamError(ctx, prefix, account, resp.StatusCode, resp.Header, respBody, quotaScope)
if s.shouldFailoverUpstreamError(resp.StatusCode) {
return nil, &UpstreamFailoverError{StatusCode: resp.StatusCode}
@@ -1314,7 +1317,7 @@ func sleepAntigravityBackoffWithContext(ctx context.Context, attempt int) bool {
}
}
-func (s *AntigravityGatewayService) handleUpstreamError(ctx context.Context, prefix string, account *Account, statusCode int, headers http.Header, body []byte) {
+func (s *AntigravityGatewayService) handleUpstreamError(ctx context.Context, prefix string, account *Account, statusCode int, headers http.Header, body []byte, quotaScope AntigravityQuotaScope) {
// 429 使用 Gemini 格式解析(从 body 解析重置时间)
if statusCode == 429 {
resetAt := ParseGeminiRateLimitResetTime(body)
@@ -1325,13 +1328,23 @@ func (s *AntigravityGatewayService) handleUpstreamError(ctx context.Context, pre
defaultDur = 5 * time.Minute
}
ra := time.Now().Add(defaultDur)
- log.Printf("%s status=429 rate_limited reset_in=%v (fallback)", prefix, defaultDur)
- _ = s.accountRepo.SetRateLimited(ctx, account.ID, ra)
+ log.Printf("%s status=429 rate_limited scope=%s reset_in=%v (fallback)", prefix, quotaScope, defaultDur)
+ if quotaScope == "" {
+ return
+ }
+ if err := s.accountRepo.SetAntigravityQuotaScopeLimit(ctx, account.ID, quotaScope, ra); err != nil {
+ log.Printf("%s status=429 rate_limit_set_failed scope=%s error=%v", prefix, quotaScope, err)
+ }
return
}
resetTime := time.Unix(*resetAt, 0)
- log.Printf("%s status=429 rate_limited reset_at=%v reset_in=%v", prefix, resetTime.Format("15:04:05"), time.Until(resetTime).Truncate(time.Second))
- _ = s.accountRepo.SetRateLimited(ctx, account.ID, resetTime)
+ log.Printf("%s status=429 rate_limited scope=%s reset_at=%v reset_in=%v", prefix, quotaScope, resetTime.Format("15:04:05"), time.Until(resetTime).Truncate(time.Second))
+ if quotaScope == "" {
+ return
+ }
+ if err := s.accountRepo.SetAntigravityQuotaScopeLimit(ctx, account.ID, quotaScope, resetTime); err != nil {
+ log.Printf("%s status=429 rate_limit_set_failed scope=%s error=%v", prefix, quotaScope, err)
+ }
return
}
// 其他错误码继续使用 rateLimitService
diff --git a/backend/internal/service/antigravity_quota_scope.go b/backend/internal/service/antigravity_quota_scope.go
new file mode 100644
index 00000000..e9f7184b
--- /dev/null
+++ b/backend/internal/service/antigravity_quota_scope.go
@@ -0,0 +1,88 @@
+package service
+
+import (
+ "strings"
+ "time"
+)
+
+const antigravityQuotaScopesKey = "antigravity_quota_scopes"
+
+// AntigravityQuotaScope 表示 Antigravity 的配额域
+type AntigravityQuotaScope string
+
+const (
+ AntigravityQuotaScopeClaude AntigravityQuotaScope = "claude"
+ AntigravityQuotaScopeGeminiText AntigravityQuotaScope = "gemini_text"
+ AntigravityQuotaScopeGeminiImage AntigravityQuotaScope = "gemini_image"
+)
+
+// resolveAntigravityQuotaScope 根据模型名称解析配额域
+func resolveAntigravityQuotaScope(requestedModel string) (AntigravityQuotaScope, bool) {
+ model := normalizeAntigravityModelName(requestedModel)
+ if model == "" {
+ return "", false
+ }
+ switch {
+ case strings.HasPrefix(model, "claude-"):
+ return AntigravityQuotaScopeClaude, true
+ case strings.HasPrefix(model, "gemini-"):
+ if isImageGenerationModel(model) {
+ return AntigravityQuotaScopeGeminiImage, true
+ }
+ return AntigravityQuotaScopeGeminiText, true
+ default:
+ return "", false
+ }
+}
+
+func normalizeAntigravityModelName(model string) string {
+ normalized := strings.ToLower(strings.TrimSpace(model))
+ normalized = strings.TrimPrefix(normalized, "models/")
+ return normalized
+}
+
+// IsSchedulableForModel 结合 Antigravity 配额域限流判断是否可调度
+func (a *Account) IsSchedulableForModel(requestedModel string) bool {
+ if a == nil {
+ return false
+ }
+ if !a.IsSchedulable() {
+ return false
+ }
+ if a.Platform != PlatformAntigravity {
+ return true
+ }
+ scope, ok := resolveAntigravityQuotaScope(requestedModel)
+ if !ok {
+ return true
+ }
+ resetAt := a.antigravityQuotaScopeResetAt(scope)
+ if resetAt == nil {
+ return true
+ }
+ now := time.Now()
+ return !now.Before(*resetAt)
+}
+
+func (a *Account) antigravityQuotaScopeResetAt(scope AntigravityQuotaScope) *time.Time {
+ if a == nil || a.Extra == nil || scope == "" {
+ return nil
+ }
+ rawScopes, ok := a.Extra[antigravityQuotaScopesKey].(map[string]any)
+ if !ok {
+ return nil
+ }
+ rawScope, ok := rawScopes[string(scope)].(map[string]any)
+ if !ok {
+ return nil
+ }
+ resetAtRaw, ok := rawScope["rate_limit_reset_at"].(string)
+ if !ok || strings.TrimSpace(resetAtRaw) == "" {
+ return nil
+ }
+ resetAt, err := time.Parse(time.RFC3339, resetAtRaw)
+ if err != nil {
+ return nil
+ }
+ return &resetAt
+}
diff --git a/backend/internal/service/auth_service.go b/backend/internal/service/auth_service.go
index e232deb3..61b15cd8 100644
--- a/backend/internal/service/auth_service.go
+++ b/backend/internal/service/auth_service.go
@@ -52,6 +52,7 @@ type AuthService struct {
emailService *EmailService
turnstileService *TurnstileService
emailQueueService *EmailQueueService
+ promoService *PromoService
}
// NewAuthService 创建认证服务实例
@@ -62,6 +63,7 @@ func NewAuthService(
emailService *EmailService,
turnstileService *TurnstileService,
emailQueueService *EmailQueueService,
+ promoService *PromoService,
) *AuthService {
return &AuthService{
userRepo: userRepo,
@@ -70,16 +72,17 @@ func NewAuthService(
emailService: emailService,
turnstileService: turnstileService,
emailQueueService: emailQueueService,
+ promoService: promoService,
}
}
// Register 用户注册,返回token和用户
func (s *AuthService) Register(ctx context.Context, email, password string) (string, *User, error) {
- return s.RegisterWithVerification(ctx, email, password, "")
+ return s.RegisterWithVerification(ctx, email, password, "", "")
}
-// RegisterWithVerification 用户注册(支持邮件验证),返回token和用户
-func (s *AuthService) RegisterWithVerification(ctx context.Context, email, password, verifyCode string) (string, *User, error) {
+// RegisterWithVerification 用户注册(支持邮件验证和优惠码),返回token和用户
+func (s *AuthService) RegisterWithVerification(ctx context.Context, email, password, verifyCode, promoCode string) (string, *User, error) {
// 检查是否开放注册(默认关闭:settingService 未配置时不允许注册)
if s.settingService == nil || !s.settingService.IsRegistrationEnabled(ctx) {
return "", nil, ErrRegDisabled
@@ -150,6 +153,19 @@ func (s *AuthService) RegisterWithVerification(ctx context.Context, email, passw
return "", nil, ErrServiceUnavailable
}
+ // 应用优惠码(如果提供)
+ if promoCode != "" && s.promoService != nil {
+ if err := s.promoService.ApplyPromoCode(ctx, user.ID, promoCode); err != nil {
+ // 优惠码应用失败不影响注册,只记录日志
+ log.Printf("[Auth] Failed to apply promo code for user %d: %v", user.ID, err)
+ } else {
+ // 重新获取用户信息以获取更新后的余额
+ if updatedUser, err := s.userRepo.GetByID(ctx, user.ID); err == nil {
+ user = updatedUser
+ }
+ }
+ }
+
// 生成token
token, err := s.GenerateToken(user)
if err != nil {
diff --git a/backend/internal/service/auth_service_register_test.go b/backend/internal/service/auth_service_register_test.go
index ab1f20a0..bc8f6f68 100644
--- a/backend/internal/service/auth_service_register_test.go
+++ b/backend/internal/service/auth_service_register_test.go
@@ -100,6 +100,7 @@ func newAuthService(repo *userRepoStub, settings map[string]string, emailCache E
emailService,
nil,
nil,
+ nil, // promoService
)
}
@@ -131,7 +132,7 @@ func TestAuthService_Register_EmailVerifyEnabledButServiceNotConfigured(t *testi
}, nil)
// 应返回服务不可用错误,而不是允许绕过验证
- _, _, err := service.RegisterWithVerification(context.Background(), "user@test.com", "password", "any-code")
+ _, _, err := service.RegisterWithVerification(context.Background(), "user@test.com", "password", "any-code", "")
require.ErrorIs(t, err, ErrServiceUnavailable)
}
@@ -143,7 +144,7 @@ func TestAuthService_Register_EmailVerifyRequired(t *testing.T) {
SettingKeyEmailVerifyEnabled: "true",
}, cache)
- _, _, err := service.RegisterWithVerification(context.Background(), "user@test.com", "password", "")
+ _, _, err := service.RegisterWithVerification(context.Background(), "user@test.com", "password", "", "")
require.ErrorIs(t, err, ErrEmailVerifyRequired)
}
@@ -157,7 +158,7 @@ func TestAuthService_Register_EmailVerifyInvalid(t *testing.T) {
SettingKeyEmailVerifyEnabled: "true",
}, cache)
- _, _, err := service.RegisterWithVerification(context.Background(), "user@test.com", "password", "wrong")
+ _, _, err := service.RegisterWithVerification(context.Background(), "user@test.com", "password", "wrong", "")
require.ErrorIs(t, err, ErrInvalidVerifyCode)
require.ErrorContains(t, err, "verify code")
}
diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go
index 3e083981..77709553 100644
--- a/backend/internal/service/domain_constants.go
+++ b/backend/internal/service/domain_constants.go
@@ -38,6 +38,12 @@ const (
RedeemTypeSubscription = "subscription"
)
+// PromoCode status constants
+const (
+ PromoCodeStatusActive = "active"
+ PromoCodeStatusDisabled = "disabled"
+)
+
// Admin adjustment type constants
const (
AdjustmentTypeAdminBalance = "admin_balance" // 管理员调整余额
diff --git a/backend/internal/service/gateway_multiplatform_test.go b/backend/internal/service/gateway_multiplatform_test.go
index 66c40e25..d863291a 100644
--- a/backend/internal/service/gateway_multiplatform_test.go
+++ b/backend/internal/service/gateway_multiplatform_test.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/stretchr/testify/require"
)
@@ -23,9 +24,11 @@ type mockAccountRepoForPlatform struct {
accounts []Account
accountsByID map[int64]*Account
listPlatformFunc func(ctx context.Context, platform string) ([]Account, error)
+ getByIDCalls int
}
func (m *mockAccountRepoForPlatform) GetByID(ctx context.Context, id int64) (*Account, error) {
+ m.getByIDCalls++
if acc, ok := m.accountsByID[id]; ok {
return acc, nil
}
@@ -136,6 +139,9 @@ func (m *mockAccountRepoForPlatform) ListSchedulableByGroupIDAndPlatforms(ctx co
func (m *mockAccountRepoForPlatform) SetRateLimited(ctx context.Context, id int64, resetAt time.Time) error {
return nil
}
+func (m *mockAccountRepoForPlatform) SetAntigravityQuotaScopeLimit(ctx context.Context, id int64, scope AntigravityQuotaScope, resetAt time.Time) error {
+ return nil
+}
func (m *mockAccountRepoForPlatform) SetOverloaded(ctx context.Context, id int64, until time.Time) error {
return nil
}
@@ -148,6 +154,9 @@ func (m *mockAccountRepoForPlatform) ClearTempUnschedulable(ctx context.Context,
func (m *mockAccountRepoForPlatform) ClearRateLimit(ctx context.Context, id int64) error {
return nil
}
+func (m *mockAccountRepoForPlatform) ClearAntigravityQuotaScopes(ctx context.Context, id int64) error {
+ return nil
+}
func (m *mockAccountRepoForPlatform) UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error {
return nil
}
@@ -185,6 +194,56 @@ func (m *mockGatewayCacheForPlatform) RefreshSessionTTL(ctx context.Context, gro
return nil
}
+type mockGroupRepoForGateway struct {
+ groups map[int64]*Group
+ getByIDCalls int
+ getByIDLiteCalls int
+}
+
+func (m *mockGroupRepoForGateway) GetByID(ctx context.Context, id int64) (*Group, error) {
+ m.getByIDCalls++
+ if g, ok := m.groups[id]; ok {
+ return g, nil
+ }
+ return nil, ErrGroupNotFound
+}
+
+func (m *mockGroupRepoForGateway) GetByIDLite(ctx context.Context, id int64) (*Group, error) {
+ m.getByIDLiteCalls++
+ if g, ok := m.groups[id]; ok {
+ return g, nil
+ }
+ return nil, ErrGroupNotFound
+}
+
+func (m *mockGroupRepoForGateway) Create(ctx context.Context, group *Group) error { return nil }
+func (m *mockGroupRepoForGateway) Update(ctx context.Context, group *Group) error { return nil }
+func (m *mockGroupRepoForGateway) Delete(ctx context.Context, id int64) error { return nil }
+func (m *mockGroupRepoForGateway) DeleteCascade(ctx context.Context, id int64) ([]int64, error) {
+ return nil, nil
+}
+func (m *mockGroupRepoForGateway) List(ctx context.Context, params pagination.PaginationParams) ([]Group, *pagination.PaginationResult, error) {
+ return nil, nil, nil
+}
+func (m *mockGroupRepoForGateway) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, status, search string, isExclusive *bool) ([]Group, *pagination.PaginationResult, error) {
+ return nil, nil, nil
+}
+func (m *mockGroupRepoForGateway) ListActive(ctx context.Context) ([]Group, error) {
+ return nil, nil
+}
+func (m *mockGroupRepoForGateway) ListActiveByPlatform(ctx context.Context, platform string) ([]Group, error) {
+ return nil, nil
+}
+func (m *mockGroupRepoForGateway) ExistsByName(ctx context.Context, name string) (bool, error) {
+ return false, nil
+}
+func (m *mockGroupRepoForGateway) GetAccountCount(ctx context.Context, groupID int64) (int64, error) {
+ return 0, nil
+}
+func (m *mockGroupRepoForGateway) DeleteAccountGroupsByGroupID(ctx context.Context, groupID int64) (int64, error) {
+ return 0, nil
+}
+
func ptr[T any](v T) *T {
return &v
}
@@ -891,6 +950,74 @@ func (m *mockConcurrencyService) GetAccountWaitingCount(ctx context.Context, acc
return m.accountWaitCounts[accountID], nil
}
+type mockConcurrencyCache struct {
+ acquireAccountCalls int
+ loadBatchCalls int
+}
+
+func (m *mockConcurrencyCache) AcquireAccountSlot(ctx context.Context, accountID int64, maxConcurrency int, requestID string) (bool, error) {
+ m.acquireAccountCalls++
+ return true, nil
+}
+
+func (m *mockConcurrencyCache) ReleaseAccountSlot(ctx context.Context, accountID int64, requestID string) error {
+ return nil
+}
+
+func (m *mockConcurrencyCache) GetAccountConcurrency(ctx context.Context, accountID int64) (int, error) {
+ return 0, nil
+}
+
+func (m *mockConcurrencyCache) IncrementAccountWaitCount(ctx context.Context, accountID int64, maxWait int) (bool, error) {
+ return true, nil
+}
+
+func (m *mockConcurrencyCache) DecrementAccountWaitCount(ctx context.Context, accountID int64) error {
+ return nil
+}
+
+func (m *mockConcurrencyCache) GetAccountWaitingCount(ctx context.Context, accountID int64) (int, error) {
+ return 0, nil
+}
+
+func (m *mockConcurrencyCache) AcquireUserSlot(ctx context.Context, userID int64, maxConcurrency int, requestID string) (bool, error) {
+ return true, nil
+}
+
+func (m *mockConcurrencyCache) ReleaseUserSlot(ctx context.Context, userID int64, requestID string) error {
+ return nil
+}
+
+func (m *mockConcurrencyCache) GetUserConcurrency(ctx context.Context, userID int64) (int, error) {
+ return 0, nil
+}
+
+func (m *mockConcurrencyCache) IncrementWaitCount(ctx context.Context, userID int64, maxWait int) (bool, error) {
+ return true, nil
+}
+
+func (m *mockConcurrencyCache) DecrementWaitCount(ctx context.Context, userID int64) error {
+ return nil
+}
+
+func (m *mockConcurrencyCache) GetAccountsLoadBatch(ctx context.Context, accounts []AccountWithConcurrency) (map[int64]*AccountLoadInfo, error) {
+ m.loadBatchCalls++
+ result := make(map[int64]*AccountLoadInfo, len(accounts))
+ for _, acc := range accounts {
+ result[acc.ID] = &AccountLoadInfo{
+ AccountID: acc.ID,
+ CurrentConcurrency: 0,
+ WaitingCount: 0,
+ LoadRate: 0,
+ }
+ }
+ return result, nil
+}
+
+func (m *mockConcurrencyCache) CleanupExpiredAccountSlots(ctx context.Context, accountID int64) error {
+ return nil
+}
+
// TestGatewayService_SelectAccountWithLoadAwareness tests load-aware account selection
func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
ctx := context.Background()
@@ -989,6 +1116,78 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
require.Equal(t, int64(2), result.Account.ID, "不应选择被排除的账号")
})
+ t.Run("粘性命中-不调用GetByID", func(t *testing.T) {
+ repo := &mockAccountRepoForPlatform{
+ accounts: []Account{
+ {ID: 1, Platform: PlatformAnthropic, Priority: 1, Status: StatusActive, Schedulable: true, Concurrency: 5},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ cache := &mockGatewayCacheForPlatform{
+ sessionBindings: map[string]int64{"sticky": 1},
+ }
+
+ cfg := testConfig()
+ cfg.Gateway.Scheduling.LoadBatchEnabled = true
+
+ concurrencyCache := &mockConcurrencyCache{}
+
+ svc := &GatewayService{
+ accountRepo: repo,
+ cache: cache,
+ cfg: cfg,
+ concurrencyService: NewConcurrencyService(concurrencyCache),
+ }
+
+ result, err := svc.SelectAccountWithLoadAwareness(ctx, nil, "sticky", "claude-3-5-sonnet-20241022", nil)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.NotNil(t, result.Account)
+ require.Equal(t, int64(1), result.Account.ID)
+ require.Equal(t, 0, repo.getByIDCalls, "粘性命中不应调用GetByID")
+ require.Equal(t, 0, concurrencyCache.loadBatchCalls, "粘性命中应在负载批量查询前返回")
+ })
+
+ t.Run("粘性账号不在候选集-回退负载感知选择", func(t *testing.T) {
+ repo := &mockAccountRepoForPlatform{
+ accounts: []Account{
+ {ID: 2, Platform: PlatformAnthropic, Priority: 1, Status: StatusActive, Schedulable: true, Concurrency: 5},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ cache := &mockGatewayCacheForPlatform{
+ sessionBindings: map[string]int64{"sticky": 1},
+ }
+
+ cfg := testConfig()
+ cfg.Gateway.Scheduling.LoadBatchEnabled = true
+
+ concurrencyCache := &mockConcurrencyCache{}
+
+ svc := &GatewayService{
+ accountRepo: repo,
+ cache: cache,
+ cfg: cfg,
+ concurrencyService: NewConcurrencyService(concurrencyCache),
+ }
+
+ result, err := svc.SelectAccountWithLoadAwareness(ctx, nil, "sticky", "claude-3-5-sonnet-20241022", nil)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.NotNil(t, result.Account)
+ require.Equal(t, int64(2), result.Account.ID, "粘性账号不在候选集时应回退到可用账号")
+ require.Equal(t, 0, repo.getByIDCalls, "粘性账号缺失不应回退到GetByID")
+ require.Equal(t, 1, concurrencyCache.loadBatchCalls, "应继续进行负载批量查询")
+ })
+
t.Run("无可用账号-返回错误", func(t *testing.T) {
repo := &mockAccountRepoForPlatform{
accounts: []Account{},
@@ -1013,3 +1212,190 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
require.Contains(t, err.Error(), "no available accounts")
})
}
+
+func TestGatewayService_GroupResolution_ReusesContextGroup(t *testing.T) {
+ ctx := context.Background()
+ groupID := int64(42)
+ group := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ Hydrated: true,
+ }
+ ctx = context.WithValue(ctx, ctxkey.Group, group)
+
+ repo := &mockAccountRepoForPlatform{
+ accounts: []Account{
+ {ID: 1, Platform: PlatformAnthropic, Priority: 1, Status: StatusActive, Schedulable: true},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ groupRepo := &mockGroupRepoForGateway{
+ groups: map[int64]*Group{groupID: group},
+ }
+
+ svc := &GatewayService{
+ accountRepo: repo,
+ groupRepo: groupRepo,
+ cfg: testConfig(),
+ }
+
+ account, err := svc.SelectAccountForModelWithExclusions(ctx, &groupID, "", "claude-3-5-sonnet-20241022", nil)
+ require.NoError(t, err)
+ require.NotNil(t, account)
+ require.Equal(t, 0, groupRepo.getByIDCalls)
+ require.Equal(t, 0, groupRepo.getByIDLiteCalls)
+}
+
+func TestGatewayService_GroupResolution_IgnoresInvalidContextGroup(t *testing.T) {
+ ctx := context.Background()
+ groupID := int64(42)
+ ctxGroup := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ }
+ ctx = context.WithValue(ctx, ctxkey.Group, ctxGroup)
+
+ repo := &mockAccountRepoForPlatform{
+ accounts: []Account{
+ {ID: 1, Platform: PlatformAnthropic, Priority: 1, Status: StatusActive, Schedulable: true},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ group := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ }
+ groupRepo := &mockGroupRepoForGateway{
+ groups: map[int64]*Group{groupID: group},
+ }
+
+ svc := &GatewayService{
+ accountRepo: repo,
+ groupRepo: groupRepo,
+ cfg: testConfig(),
+ }
+
+ account, err := svc.SelectAccountForModelWithExclusions(ctx, &groupID, "", "claude-3-5-sonnet-20241022", nil)
+ require.NoError(t, err)
+ require.NotNil(t, account)
+ require.Equal(t, 0, groupRepo.getByIDCalls)
+ require.Equal(t, 1, groupRepo.getByIDLiteCalls)
+}
+
+func TestGatewayService_GroupContext_OverwritesInvalidContextGroup(t *testing.T) {
+ groupID := int64(42)
+ invalidGroup := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ }
+ hydratedGroup := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ Hydrated: true,
+ }
+
+ ctx := context.WithValue(context.Background(), ctxkey.Group, invalidGroup)
+ svc := &GatewayService{}
+ ctx = svc.withGroupContext(ctx, hydratedGroup)
+
+ got, ok := ctx.Value(ctxkey.Group).(*Group)
+ require.True(t, ok)
+ require.Same(t, hydratedGroup, got)
+}
+
+func TestGatewayService_GroupResolution_FallbackUsesLiteOnce(t *testing.T) {
+ ctx := context.Background()
+ groupID := int64(10)
+ fallbackID := int64(11)
+ group := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ ClaudeCodeOnly: true,
+ FallbackGroupID: &fallbackID,
+ Hydrated: true,
+ }
+ fallbackGroup := &Group{
+ ID: fallbackID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ }
+ ctx = context.WithValue(ctx, ctxkey.Group, group)
+
+ repo := &mockAccountRepoForPlatform{
+ accounts: []Account{
+ {ID: 1, Platform: PlatformAnthropic, Priority: 1, Status: StatusActive, Schedulable: true},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ groupRepo := &mockGroupRepoForGateway{
+ groups: map[int64]*Group{fallbackID: fallbackGroup},
+ }
+
+ svc := &GatewayService{
+ accountRepo: repo,
+ groupRepo: groupRepo,
+ cfg: testConfig(),
+ }
+
+ account, err := svc.SelectAccountForModelWithExclusions(ctx, &groupID, "", "claude-3-5-sonnet-20241022", nil)
+ require.NoError(t, err)
+ require.NotNil(t, account)
+ require.Equal(t, 0, groupRepo.getByIDCalls)
+ require.Equal(t, 1, groupRepo.getByIDLiteCalls)
+}
+
+func TestGatewayService_ResolveGatewayGroup_DetectsFallbackCycle(t *testing.T) {
+ ctx := context.Background()
+ groupID := int64(10)
+ fallbackID := int64(11)
+
+ group := &Group{
+ ID: groupID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ ClaudeCodeOnly: true,
+ FallbackGroupID: &fallbackID,
+ }
+ fallbackGroup := &Group{
+ ID: fallbackID,
+ Platform: PlatformAnthropic,
+ Status: StatusActive,
+ ClaudeCodeOnly: true,
+ FallbackGroupID: &groupID,
+ }
+
+ groupRepo := &mockGroupRepoForGateway{
+ groups: map[int64]*Group{
+ groupID: group,
+ fallbackID: fallbackGroup,
+ },
+ }
+
+ svc := &GatewayService{
+ groupRepo: groupRepo,
+ }
+
+ gotGroup, gotID, err := svc.resolveGatewayGroup(ctx, &groupID)
+ require.Error(t, err)
+ require.Nil(t, gotGroup)
+ require.Nil(t, gotID)
+ require.Contains(t, err.Error(), "fallback group cycle")
+}
diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go
index 89f7d798..31148b17 100644
--- a/backend/internal/service/gateway_service.go
+++ b/backend/internal/service/gateway_service.go
@@ -33,7 +33,7 @@ const (
claudeAPIURL = "https://api.anthropic.com/v1/messages?beta=true"
claudeAPICountTokensURL = "https://api.anthropic.com/v1/messages/count_tokens?beta=true"
stickySessionTTL = time.Hour // 粘性会话TTL
- defaultMaxLineSize = 10 * 1024 * 1024
+ defaultMaxLineSize = 40 * 1024 * 1024
claudeCodeSystemPrompt = "You are Claude Code, Anthropic's official CLI for Claude."
maxCacheControlBlocks = 4 // Anthropic API 允许的最大 cache_control 块数量
)
@@ -361,27 +361,13 @@ func (s *GatewayService) SelectAccountForModelWithExclusions(ctx context.Context
if hasForcePlatform && forcePlatform != "" {
platform = forcePlatform
} else if groupID != nil {
- // 根据分组 platform 决定查询哪种账号
- group, err := s.groupRepo.GetByID(ctx, *groupID)
+ group, resolvedGroupID, err := s.resolveGatewayGroup(ctx, groupID)
if err != nil {
- return nil, fmt.Errorf("get group failed: %w", err)
+ return nil, err
}
+ groupID = resolvedGroupID
+ ctx = s.withGroupContext(ctx, group)
platform = group.Platform
-
- // 检查 Claude Code 客户端限制
- if group.ClaudeCodeOnly {
- isClaudeCode := IsClaudeCodeClient(ctx)
- if !isClaudeCode {
- // 非 Claude Code 客户端,检查是否有降级分组
- if group.FallbackGroupID != nil {
- // 使用降级分组重新调度
- fallbackGroupID := *group.FallbackGroupID
- return s.SelectAccountForModelWithExclusions(ctx, &fallbackGroupID, sessionHash, requestedModel, excludedIDs)
- }
- // 无降级分组,拒绝访问
- return nil, ErrClaudeCodeOnly
- }
- }
} else {
// 无分组时只使用原生 anthropic 平台
platform = PlatformAnthropic
@@ -409,10 +395,11 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
}
// 检查 Claude Code 客户端限制(可能会替换 groupID 为降级分组)
- groupID, err := s.checkClaudeCodeRestriction(ctx, groupID)
+ group, groupID, err := s.checkClaudeCodeRestriction(ctx, groupID)
if err != nil {
return nil, err
}
+ ctx = s.withGroupContext(ctx, group)
if s.concurrencyService == nil || !cfg.LoadBatchEnabled {
account, err := s.SelectAccountForModelWithExclusions(ctx, groupID, sessionHash, requestedModel, excludedIDs)
@@ -452,7 +439,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
}, nil
}
- platform, hasForcePlatform, err := s.resolvePlatform(ctx, groupID)
+ platform, hasForcePlatform, err := s.resolvePlatform(ctx, groupID, group)
if err != nil {
return nil, err
}
@@ -478,10 +465,15 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if sessionHash != "" && s.cache != nil {
accountID, err := s.cache.GetSessionAccountID(ctx, derefGroupID(groupID), sessionHash)
if err == nil && accountID > 0 && !isExcluded(accountID) {
- account, err := s.accountRepo.GetByID(ctx, accountID)
- if err == nil && s.isAccountInGroup(account, groupID) &&
+ // 粘性命中仅在当前可调度候选集中生效。
+ accountByID := make(map[int64]*Account, len(accounts))
+ for i := range accounts {
+ accountByID[accounts[i].ID] = &accounts[i]
+ }
+ account, ok := accountByID[accountID]
+ if ok && s.isAccountInGroup(account, groupID) &&
s.isAccountAllowedForPlatform(account, platform, useMixed) &&
- account.IsSchedulable() &&
+ account.IsSchedulableForModel(requestedModel) &&
(requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
result, err := s.tryAcquireAccountSlot(ctx, accountID, account.Concurrency)
if err == nil && result.Acquired {
@@ -519,6 +511,9 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
if !s.isAccountAllowedForPlatform(acc, platform, useMixed) {
continue
}
+ if !acc.IsSchedulableForModel(requestedModel) {
+ continue
+ }
if requestedModel != "" && !s.isModelSupportedByAccount(acc, requestedModel) {
continue
}
@@ -652,51 +647,97 @@ func (s *GatewayService) schedulingConfig() config.GatewaySchedulingConfig {
}
}
+func (s *GatewayService) withGroupContext(ctx context.Context, group *Group) context.Context {
+ if !IsGroupContextValid(group) {
+ return ctx
+ }
+ if existing, ok := ctx.Value(ctxkey.Group).(*Group); ok && existing != nil && existing.ID == group.ID && IsGroupContextValid(existing) {
+ return ctx
+ }
+ return context.WithValue(ctx, ctxkey.Group, group)
+}
+
+func (s *GatewayService) groupFromContext(ctx context.Context, groupID int64) *Group {
+ if group, ok := ctx.Value(ctxkey.Group).(*Group); ok && IsGroupContextValid(group) && group.ID == groupID {
+ return group
+ }
+ return nil
+}
+
+func (s *GatewayService) resolveGroupByID(ctx context.Context, groupID int64) (*Group, error) {
+ if group := s.groupFromContext(ctx, groupID); group != nil {
+ return group, nil
+ }
+ group, err := s.groupRepo.GetByIDLite(ctx, groupID)
+ if err != nil {
+ return nil, fmt.Errorf("get group failed: %w", err)
+ }
+ return group, nil
+}
+
+func (s *GatewayService) resolveGatewayGroup(ctx context.Context, groupID *int64) (*Group, *int64, error) {
+ if groupID == nil {
+ return nil, nil, nil
+ }
+
+ currentID := *groupID
+ visited := map[int64]struct{}{}
+ for {
+ if _, seen := visited[currentID]; seen {
+ return nil, nil, fmt.Errorf("fallback group cycle detected")
+ }
+ visited[currentID] = struct{}{}
+
+ group, err := s.resolveGroupByID(ctx, currentID)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if !group.ClaudeCodeOnly || IsClaudeCodeClient(ctx) {
+ return group, ¤tID, nil
+ }
+
+ if group.FallbackGroupID == nil {
+ return nil, nil, ErrClaudeCodeOnly
+ }
+ currentID = *group.FallbackGroupID
+ }
+}
+
// checkClaudeCodeRestriction 检查分组的 Claude Code 客户端限制
// 如果分组启用了 claude_code_only 且请求不是来自 Claude Code 客户端:
// - 有降级分组:返回降级分组的 ID
// - 无降级分组:返回 ErrClaudeCodeOnly 错误
-func (s *GatewayService) checkClaudeCodeRestriction(ctx context.Context, groupID *int64) (*int64, error) {
+func (s *GatewayService) checkClaudeCodeRestriction(ctx context.Context, groupID *int64) (*Group, *int64, error) {
if groupID == nil {
- return groupID, nil
+ return nil, groupID, nil
}
// 强制平台模式不检查 Claude Code 限制
if _, hasForcePlatform := ctx.Value(ctxkey.ForcePlatform).(string); hasForcePlatform {
- return groupID, nil
+ return nil, groupID, nil
}
- group, err := s.groupRepo.GetByID(ctx, *groupID)
+ group, resolvedID, err := s.resolveGatewayGroup(ctx, groupID)
if err != nil {
- return nil, fmt.Errorf("get group failed: %w", err)
+ return nil, nil, err
}
- if !group.ClaudeCodeOnly {
- return groupID, nil
- }
-
- // 分组启用了 Claude Code 限制
- if IsClaudeCodeClient(ctx) {
- return groupID, nil
- }
-
- // 非 Claude Code 客户端,检查降级分组
- if group.FallbackGroupID != nil {
- return group.FallbackGroupID, nil
- }
-
- return nil, ErrClaudeCodeOnly
+ return group, resolvedID, nil
}
-func (s *GatewayService) resolvePlatform(ctx context.Context, groupID *int64) (string, bool, error) {
+func (s *GatewayService) resolvePlatform(ctx context.Context, groupID *int64, group *Group) (string, bool, error) {
forcePlatform, hasForcePlatform := ctx.Value(ctxkey.ForcePlatform).(string)
if hasForcePlatform && forcePlatform != "" {
return forcePlatform, true, nil
}
+ if group != nil {
+ return group.Platform, false, nil
+ }
if groupID != nil {
- group, err := s.groupRepo.GetByID(ctx, *groupID)
+ group, err := s.resolveGroupByID(ctx, *groupID)
if err != nil {
- return "", false, fmt.Errorf("get group failed: %w", err)
+ return "", false, err
}
return group.Platform, false, nil
}
@@ -812,7 +853,7 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if _, excluded := excludedIDs[accountID]; !excluded {
account, err := s.accountRepo.GetByID(ctx, accountID)
// 检查账号分组归属和平台匹配(确保粘性会话不会跨分组或跨平台)
- if err == nil && s.isAccountInGroup(account, groupID) && account.Platform == platform && account.IsSchedulable() && (requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
+ if err == nil && s.isAccountInGroup(account, groupID) && account.Platform == platform && account.IsSchedulableForModel(requestedModel) && (requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
if err := s.cache.RefreshSessionTTL(ctx, derefGroupID(groupID), sessionHash, stickySessionTTL); err != nil {
log.Printf("refresh session ttl failed: session=%s err=%v", sessionHash, err)
}
@@ -844,6 +885,9 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if _, excluded := excludedIDs[acc.ID]; excluded {
continue
}
+ if !acc.IsSchedulableForModel(requestedModel) {
+ continue
+ }
if requestedModel != "" && !s.isModelSupportedByAccount(acc, requestedModel) {
continue
}
@@ -901,7 +945,7 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if _, excluded := excludedIDs[accountID]; !excluded {
account, err := s.accountRepo.GetByID(ctx, accountID)
// 检查账号分组归属和有效性:原生平台直接匹配,antigravity 需要启用混合调度
- if err == nil && s.isAccountInGroup(account, groupID) && account.IsSchedulable() && (requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
+ if err == nil && s.isAccountInGroup(account, groupID) && account.IsSchedulableForModel(requestedModel) && (requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
if account.Platform == nativePlatform || (account.Platform == PlatformAntigravity && account.IsMixedSchedulingEnabled()) {
if err := s.cache.RefreshSessionTTL(ctx, derefGroupID(groupID), sessionHash, stickySessionTTL); err != nil {
log.Printf("refresh session ttl failed: session=%s err=%v", sessionHash, err)
@@ -936,6 +980,9 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if acc.Platform == PlatformAntigravity && !acc.IsMixedSchedulingEnabled() {
continue
}
+ if !acc.IsSchedulableForModel(requestedModel) {
+ continue
+ }
if requestedModel != "" && !s.isModelSupportedByAccount(acc, requestedModel) {
continue
}
diff --git a/backend/internal/service/gemini_messages_compat_service.go b/backend/internal/service/gemini_messages_compat_service.go
index f2b5bafd..78452b1e 100644
--- a/backend/internal/service/gemini_messages_compat_service.go
+++ b/backend/internal/service/gemini_messages_compat_service.go
@@ -86,9 +86,15 @@ func (s *GeminiMessagesCompatService) SelectAccountForModelWithExclusions(ctx co
platform = forcePlatform
} else if groupID != nil {
// 根据分组 platform 决定查询哪种账号
- group, err := s.groupRepo.GetByID(ctx, *groupID)
- if err != nil {
- return nil, fmt.Errorf("get group failed: %w", err)
+ var group *Group
+ if ctxGroup, ok := ctx.Value(ctxkey.Group).(*Group); ok && IsGroupContextValid(ctxGroup) && ctxGroup.ID == *groupID {
+ group = ctxGroup
+ } else {
+ var err error
+ group, err = s.groupRepo.GetByIDLite(ctx, *groupID)
+ if err != nil {
+ return nil, fmt.Errorf("get group failed: %w", err)
+ }
}
platform = group.Platform
} else {
@@ -114,7 +120,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForModelWithExclusions(ctx co
if _, excluded := excludedIDs[accountID]; !excluded {
account, err := s.accountRepo.GetByID(ctx, accountID)
// 检查账号是否有效:原生平台直接匹配,antigravity 需要启用混合调度
- if err == nil && account.IsSchedulable() && (requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
+ if err == nil && account.IsSchedulableForModel(requestedModel) && (requestedModel == "" || s.isModelSupportedByAccount(account, requestedModel)) {
valid := false
if account.Platform == platform {
valid = true
@@ -172,6 +178,9 @@ func (s *GeminiMessagesCompatService) SelectAccountForModelWithExclusions(ctx co
if useMixedScheduling && acc.Platform == PlatformAntigravity && !acc.IsMixedSchedulingEnabled() {
continue
}
+ if !acc.IsSchedulableForModel(requestedModel) {
+ continue
+ }
if requestedModel != "" && !s.isModelSupportedByAccount(acc, requestedModel) {
continue
}
diff --git a/backend/internal/service/gemini_multiplatform_test.go b/backend/internal/service/gemini_multiplatform_test.go
index 1a8b005f..c99cb87d 100644
--- a/backend/internal/service/gemini_multiplatform_test.go
+++ b/backend/internal/service/gemini_multiplatform_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"time"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/stretchr/testify/require"
)
@@ -121,6 +122,9 @@ func (m *mockAccountRepoForGemini) ListSchedulableByGroupIDAndPlatforms(ctx cont
func (m *mockAccountRepoForGemini) SetRateLimited(ctx context.Context, id int64, resetAt time.Time) error {
return nil
}
+func (m *mockAccountRepoForGemini) SetAntigravityQuotaScopeLimit(ctx context.Context, id int64, scope AntigravityQuotaScope, resetAt time.Time) error {
+ return nil
+}
func (m *mockAccountRepoForGemini) SetOverloaded(ctx context.Context, id int64, until time.Time) error {
return nil
}
@@ -131,6 +135,9 @@ func (m *mockAccountRepoForGemini) ClearTempUnschedulable(ctx context.Context, i
return nil
}
func (m *mockAccountRepoForGemini) ClearRateLimit(ctx context.Context, id int64) error { return nil }
+func (m *mockAccountRepoForGemini) ClearAntigravityQuotaScopes(ctx context.Context, id int64) error {
+ return nil
+}
func (m *mockAccountRepoForGemini) UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error {
return nil
}
@@ -146,10 +153,21 @@ var _ AccountRepository = (*mockAccountRepoForGemini)(nil)
// mockGroupRepoForGemini Gemini 测试用的 group repo mock
type mockGroupRepoForGemini struct {
- groups map[int64]*Group
+ groups map[int64]*Group
+ getByIDCalls int
+ getByIDLiteCalls int
}
func (m *mockGroupRepoForGemini) GetByID(ctx context.Context, id int64) (*Group, error) {
+ m.getByIDCalls++
+ if g, ok := m.groups[id]; ok {
+ return g, nil
+ }
+ return nil, errors.New("group not found")
+}
+
+func (m *mockGroupRepoForGemini) GetByIDLite(ctx context.Context, id int64) (*Group, error) {
+ m.getByIDLiteCalls++
if g, ok := m.groups[id]; ok {
return g, nil
}
@@ -242,6 +260,77 @@ func TestGeminiMessagesCompatService_SelectAccountForModelWithExclusions_GeminiP
require.Equal(t, PlatformGemini, acc.Platform, "无分组时应只返回 gemini 平台账户")
}
+func TestGeminiMessagesCompatService_GroupResolution_ReusesContextGroup(t *testing.T) {
+ ctx := context.Background()
+ groupID := int64(7)
+ group := &Group{
+ ID: groupID,
+ Platform: PlatformGemini,
+ Status: StatusActive,
+ Hydrated: true,
+ }
+ ctx = context.WithValue(ctx, ctxkey.Group, group)
+
+ repo := &mockAccountRepoForGemini{
+ accounts: []Account{
+ {ID: 1, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ cache := &mockGatewayCacheForGemini{}
+ groupRepo := &mockGroupRepoForGemini{groups: map[int64]*Group{}}
+
+ svc := &GeminiMessagesCompatService{
+ accountRepo: repo,
+ groupRepo: groupRepo,
+ cache: cache,
+ }
+
+ acc, err := svc.SelectAccountForModelWithExclusions(ctx, &groupID, "", "gemini-2.5-flash", nil)
+ require.NoError(t, err)
+ require.NotNil(t, acc)
+ require.Equal(t, 0, groupRepo.getByIDCalls)
+ require.Equal(t, 0, groupRepo.getByIDLiteCalls)
+}
+
+func TestGeminiMessagesCompatService_GroupResolution_UsesLiteFetch(t *testing.T) {
+ ctx := context.Background()
+ groupID := int64(7)
+
+ repo := &mockAccountRepoForGemini{
+ accounts: []Account{
+ {ID: 1, Platform: PlatformGemini, Priority: 1, Status: StatusActive, Schedulable: true},
+ },
+ accountsByID: map[int64]*Account{},
+ }
+ for i := range repo.accounts {
+ repo.accountsByID[repo.accounts[i].ID] = &repo.accounts[i]
+ }
+
+ cache := &mockGatewayCacheForGemini{}
+ groupRepo := &mockGroupRepoForGemini{
+ groups: map[int64]*Group{
+ groupID: {ID: groupID, Platform: PlatformGemini},
+ },
+ }
+
+ svc := &GeminiMessagesCompatService{
+ accountRepo: repo,
+ groupRepo: groupRepo,
+ cache: cache,
+ }
+
+ acc, err := svc.SelectAccountForModelWithExclusions(ctx, &groupID, "", "gemini-2.5-flash", nil)
+ require.NoError(t, err)
+ require.NotNil(t, acc)
+ require.Equal(t, 0, groupRepo.getByIDCalls)
+ require.Equal(t, 1, groupRepo.getByIDLiteCalls)
+}
+
// TestGeminiMessagesCompatService_SelectAccountForModelWithExclusions_AntigravityGroup 测试 antigravity 分组
func TestGeminiMessagesCompatService_SelectAccountForModelWithExclusions_AntigravityGroup(t *testing.T) {
ctx := context.Background()
diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go
index 80d89074..8e8d47d6 100644
--- a/backend/internal/service/group.go
+++ b/backend/internal/service/group.go
@@ -10,6 +10,7 @@ type Group struct {
RateMultiplier float64
IsExclusive bool
Status string
+ Hydrated bool // indicates the group was loaded from a trusted repository source
SubscriptionType string
DailyLimitUSD *float64
@@ -72,3 +73,20 @@ func (g *Group) GetImagePrice(imageSize string) *float64 {
return g.ImagePrice2K
}
}
+
+// IsGroupContextValid reports whether a group from context has the fields required for routing decisions.
+func IsGroupContextValid(group *Group) bool {
+ if group == nil {
+ return false
+ }
+ if group.ID <= 0 {
+ return false
+ }
+ if !group.Hydrated {
+ return false
+ }
+ if group.Platform == "" || group.Status == "" {
+ return false
+ }
+ return true
+}
diff --git a/backend/internal/service/group_service.go b/backend/internal/service/group_service.go
index a444556f..2f0f4975 100644
--- a/backend/internal/service/group_service.go
+++ b/backend/internal/service/group_service.go
@@ -16,6 +16,7 @@ var (
type GroupRepository interface {
Create(ctx context.Context, group *Group) error
GetByID(ctx context.Context, id int64) (*Group, error)
+ GetByIDLite(ctx context.Context, id int64) (*Group, error)
Update(ctx context.Context, group *Group) error
Delete(ctx context.Context, id int64) error
DeleteCascade(ctx context.Context, id int64) ([]int64, error)
diff --git a/backend/internal/service/promo_code.go b/backend/internal/service/promo_code.go
new file mode 100644
index 00000000..94e733a8
--- /dev/null
+++ b/backend/internal/service/promo_code.go
@@ -0,0 +1,73 @@
+package service
+
+import (
+ "time"
+)
+
+// PromoCode 注册优惠码
+type PromoCode struct {
+ ID int64
+ Code string
+ BonusAmount float64
+ MaxUses int
+ UsedCount int
+ Status string
+ ExpiresAt *time.Time
+ Notes string
+ CreatedAt time.Time
+ UpdatedAt time.Time
+
+ // 关联
+ UsageRecords []PromoCodeUsage
+}
+
+// PromoCodeUsage 优惠码使用记录
+type PromoCodeUsage struct {
+ ID int64
+ PromoCodeID int64
+ UserID int64
+ BonusAmount float64
+ UsedAt time.Time
+
+ // 关联
+ PromoCode *PromoCode
+ User *User
+}
+
+// CanUse 检查优惠码是否可用
+func (p *PromoCode) CanUse() bool {
+ if p.Status != PromoCodeStatusActive {
+ return false
+ }
+ if p.ExpiresAt != nil && time.Now().After(*p.ExpiresAt) {
+ return false
+ }
+ if p.MaxUses > 0 && p.UsedCount >= p.MaxUses {
+ return false
+ }
+ return true
+}
+
+// IsExpired 检查是否已过期
+func (p *PromoCode) IsExpired() bool {
+ return p.ExpiresAt != nil && time.Now().After(*p.ExpiresAt)
+}
+
+// CreatePromoCodeInput 创建优惠码输入
+type CreatePromoCodeInput struct {
+ Code string
+ BonusAmount float64
+ MaxUses int
+ ExpiresAt *time.Time
+ Notes string
+}
+
+// UpdatePromoCodeInput 更新优惠码输入
+type UpdatePromoCodeInput struct {
+ Code *string
+ BonusAmount *float64
+ MaxUses *int
+ Status *string
+ ExpiresAt *time.Time
+ Notes *string
+}
diff --git a/backend/internal/service/promo_code_repository.go b/backend/internal/service/promo_code_repository.go
new file mode 100644
index 00000000..f55f9a6b
--- /dev/null
+++ b/backend/internal/service/promo_code_repository.go
@@ -0,0 +1,30 @@
+package service
+
+import (
+ "context"
+
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+)
+
+// PromoCodeRepository 优惠码仓储接口
+type PromoCodeRepository interface {
+ // 基础 CRUD
+ Create(ctx context.Context, code *PromoCode) error
+ GetByID(ctx context.Context, id int64) (*PromoCode, error)
+ GetByCode(ctx context.Context, code string) (*PromoCode, error)
+ GetByCodeForUpdate(ctx context.Context, code string) (*PromoCode, error) // 带行锁的查询,用于并发控制
+ Update(ctx context.Context, code *PromoCode) error
+ Delete(ctx context.Context, id int64) error
+
+ // 列表查询
+ List(ctx context.Context, params pagination.PaginationParams) ([]PromoCode, *pagination.PaginationResult, error)
+ ListWithFilters(ctx context.Context, params pagination.PaginationParams, status, search string) ([]PromoCode, *pagination.PaginationResult, error)
+
+ // 使用记录
+ CreateUsage(ctx context.Context, usage *PromoCodeUsage) error
+ GetUsageByPromoCodeAndUser(ctx context.Context, promoCodeID, userID int64) (*PromoCodeUsage, error)
+ ListUsagesByPromoCode(ctx context.Context, promoCodeID int64, params pagination.PaginationParams) ([]PromoCodeUsage, *pagination.PaginationResult, error)
+
+ // 计数操作
+ IncrementUsedCount(ctx context.Context, id int64) error
+}
diff --git a/backend/internal/service/promo_service.go b/backend/internal/service/promo_service.go
new file mode 100644
index 00000000..9acd5868
--- /dev/null
+++ b/backend/internal/service/promo_service.go
@@ -0,0 +1,256 @@
+package service
+
+import (
+ "context"
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "time"
+
+ dbent "github.com/Wei-Shaw/sub2api/ent"
+ infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+)
+
+var (
+ ErrPromoCodeNotFound = infraerrors.NotFound("PROMO_CODE_NOT_FOUND", "promo code not found")
+ ErrPromoCodeExpired = infraerrors.BadRequest("PROMO_CODE_EXPIRED", "promo code has expired")
+ ErrPromoCodeDisabled = infraerrors.BadRequest("PROMO_CODE_DISABLED", "promo code is disabled")
+ ErrPromoCodeMaxUsed = infraerrors.BadRequest("PROMO_CODE_MAX_USED", "promo code has reached maximum uses")
+ ErrPromoCodeAlreadyUsed = infraerrors.Conflict("PROMO_CODE_ALREADY_USED", "you have already used this promo code")
+ ErrPromoCodeInvalid = infraerrors.BadRequest("PROMO_CODE_INVALID", "invalid promo code")
+)
+
+// PromoService 优惠码服务
+type PromoService struct {
+ promoRepo PromoCodeRepository
+ userRepo UserRepository
+ billingCacheService *BillingCacheService
+ entClient *dbent.Client
+}
+
+// NewPromoService 创建优惠码服务实例
+func NewPromoService(
+ promoRepo PromoCodeRepository,
+ userRepo UserRepository,
+ billingCacheService *BillingCacheService,
+ entClient *dbent.Client,
+) *PromoService {
+ return &PromoService{
+ promoRepo: promoRepo,
+ userRepo: userRepo,
+ billingCacheService: billingCacheService,
+ entClient: entClient,
+ }
+}
+
+// ValidatePromoCode 验证优惠码(注册前调用)
+// 返回 nil, nil 表示空码(不报错)
+func (s *PromoService) ValidatePromoCode(ctx context.Context, code string) (*PromoCode, error) {
+ code = strings.TrimSpace(code)
+ if code == "" {
+ return nil, nil // 空码不报错,直接返回
+ }
+
+ promoCode, err := s.promoRepo.GetByCode(ctx, code)
+ if err != nil {
+ // 保留原始错误类型,不要统一映射为 NotFound
+ return nil, err
+ }
+
+ if err := s.validatePromoCodeStatus(promoCode); err != nil {
+ return nil, err
+ }
+
+ return promoCode, nil
+}
+
+// validatePromoCodeStatus 验证优惠码状态
+func (s *PromoService) validatePromoCodeStatus(promoCode *PromoCode) error {
+ if !promoCode.CanUse() {
+ if promoCode.IsExpired() {
+ return ErrPromoCodeExpired
+ }
+ if promoCode.Status == PromoCodeStatusDisabled {
+ return ErrPromoCodeDisabled
+ }
+ if promoCode.MaxUses > 0 && promoCode.UsedCount >= promoCode.MaxUses {
+ return ErrPromoCodeMaxUsed
+ }
+ return ErrPromoCodeInvalid
+ }
+ return nil
+}
+
+// ApplyPromoCode 应用优惠码(注册成功后调用)
+// 使用事务和行锁确保并发安全
+func (s *PromoService) ApplyPromoCode(ctx context.Context, userID int64, code string) error {
+ code = strings.TrimSpace(code)
+ if code == "" {
+ return nil
+ }
+
+ // 开启事务
+ tx, err := s.entClient.Tx(ctx)
+ if err != nil {
+ return fmt.Errorf("begin transaction: %w", err)
+ }
+ defer func() { _ = tx.Rollback() }()
+
+ txCtx := dbent.NewTxContext(ctx, tx)
+
+ // 在事务中获取并锁定优惠码记录(FOR UPDATE)
+ promoCode, err := s.promoRepo.GetByCodeForUpdate(txCtx, code)
+ if err != nil {
+ return err
+ }
+
+ // 在事务中验证优惠码状态
+ if err := s.validatePromoCodeStatus(promoCode); err != nil {
+ return err
+ }
+
+ // 在事务中检查用户是否已使用过此优惠码
+ existing, err := s.promoRepo.GetUsageByPromoCodeAndUser(txCtx, promoCode.ID, userID)
+ if err != nil {
+ return fmt.Errorf("check existing usage: %w", err)
+ }
+ if existing != nil {
+ return ErrPromoCodeAlreadyUsed
+ }
+
+ // 增加用户余额
+ if err := s.userRepo.UpdateBalance(txCtx, userID, promoCode.BonusAmount); err != nil {
+ return fmt.Errorf("update user balance: %w", err)
+ }
+
+ // 创建使用记录
+ usage := &PromoCodeUsage{
+ PromoCodeID: promoCode.ID,
+ UserID: userID,
+ BonusAmount: promoCode.BonusAmount,
+ UsedAt: time.Now(),
+ }
+ if err := s.promoRepo.CreateUsage(txCtx, usage); err != nil {
+ return fmt.Errorf("create usage record: %w", err)
+ }
+
+ // 增加使用次数
+ if err := s.promoRepo.IncrementUsedCount(txCtx, promoCode.ID); err != nil {
+ return fmt.Errorf("increment used count: %w", err)
+ }
+
+ if err := tx.Commit(); err != nil {
+ return fmt.Errorf("commit transaction: %w", err)
+ }
+
+ // 失效余额缓存
+ if s.billingCacheService != nil {
+ go func() {
+ cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ _ = s.billingCacheService.InvalidateUserBalance(cacheCtx, userID)
+ }()
+ }
+
+ return nil
+}
+
+// GenerateRandomCode 生成随机优惠码
+func (s *PromoService) GenerateRandomCode() (string, error) {
+ bytes := make([]byte, 8)
+ if _, err := rand.Read(bytes); err != nil {
+ return "", fmt.Errorf("generate random bytes: %w", err)
+ }
+ return strings.ToUpper(hex.EncodeToString(bytes)), nil
+}
+
+// Create 创建优惠码
+func (s *PromoService) Create(ctx context.Context, input *CreatePromoCodeInput) (*PromoCode, error) {
+ code := strings.TrimSpace(input.Code)
+ if code == "" {
+ // 自动生成
+ var err error
+ code, err = s.GenerateRandomCode()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ promoCode := &PromoCode{
+ Code: strings.ToUpper(code),
+ BonusAmount: input.BonusAmount,
+ MaxUses: input.MaxUses,
+ UsedCount: 0,
+ Status: PromoCodeStatusActive,
+ ExpiresAt: input.ExpiresAt,
+ Notes: input.Notes,
+ }
+
+ if err := s.promoRepo.Create(ctx, promoCode); err != nil {
+ return nil, fmt.Errorf("create promo code: %w", err)
+ }
+
+ return promoCode, nil
+}
+
+// GetByID 根据ID获取优惠码
+func (s *PromoService) GetByID(ctx context.Context, id int64) (*PromoCode, error) {
+ code, err := s.promoRepo.GetByID(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+ return code, nil
+}
+
+// Update 更新优惠码
+func (s *PromoService) Update(ctx context.Context, id int64, input *UpdatePromoCodeInput) (*PromoCode, error) {
+ promoCode, err := s.promoRepo.GetByID(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ if input.Code != nil {
+ promoCode.Code = strings.ToUpper(strings.TrimSpace(*input.Code))
+ }
+ if input.BonusAmount != nil {
+ promoCode.BonusAmount = *input.BonusAmount
+ }
+ if input.MaxUses != nil {
+ promoCode.MaxUses = *input.MaxUses
+ }
+ if input.Status != nil {
+ promoCode.Status = *input.Status
+ }
+ if input.ExpiresAt != nil {
+ promoCode.ExpiresAt = input.ExpiresAt
+ }
+ if input.Notes != nil {
+ promoCode.Notes = *input.Notes
+ }
+
+ if err := s.promoRepo.Update(ctx, promoCode); err != nil {
+ return nil, fmt.Errorf("update promo code: %w", err)
+ }
+
+ return promoCode, nil
+}
+
+// Delete 删除优惠码
+func (s *PromoService) Delete(ctx context.Context, id int64) error {
+ if err := s.promoRepo.Delete(ctx, id); err != nil {
+ return fmt.Errorf("delete promo code: %w", err)
+ }
+ return nil
+}
+
+// List 获取优惠码列表
+func (s *PromoService) List(ctx context.Context, params pagination.PaginationParams, status, search string) ([]PromoCode, *pagination.PaginationResult, error) {
+ return s.promoRepo.ListWithFilters(ctx, params, status, search)
+}
+
+// ListUsages 获取使用记录
+func (s *PromoService) ListUsages(ctx context.Context, promoCodeID int64, params pagination.PaginationParams) ([]PromoCodeUsage, *pagination.PaginationResult, error) {
+ return s.promoRepo.ListUsagesByPromoCode(ctx, promoCodeID, params)
+}
diff --git a/backend/internal/service/ratelimit_service.go b/backend/internal/service/ratelimit_service.go
index 196f1643..f1362646 100644
--- a/backend/internal/service/ratelimit_service.go
+++ b/backend/internal/service/ratelimit_service.go
@@ -345,7 +345,7 @@ func (s *RateLimitService) UpdateSessionWindow(ctx context.Context, account *Acc
// 如果状态为allowed且之前有限流,说明窗口已重置,清除限流状态
if status == "allowed" && account.IsRateLimited() {
- if err := s.accountRepo.ClearRateLimit(ctx, account.ID); err != nil {
+ if err := s.ClearRateLimit(ctx, account.ID); err != nil {
log.Printf("ClearRateLimit failed for account %d: %v", account.ID, err)
}
}
@@ -353,7 +353,10 @@ func (s *RateLimitService) UpdateSessionWindow(ctx context.Context, account *Acc
// ClearRateLimit 清除账号的限流状态
func (s *RateLimitService) ClearRateLimit(ctx context.Context, accountID int64) error {
- return s.accountRepo.ClearRateLimit(ctx, accountID)
+ if err := s.accountRepo.ClearRateLimit(ctx, accountID); err != nil {
+ return err
+ }
+ return s.accountRepo.ClearAntigravityQuotaScopes(ctx, accountID)
}
func (s *RateLimitService) ClearTempUnschedulable(ctx context.Context, accountID int64) error {
diff --git a/backend/internal/service/wire.go b/backend/internal/service/wire.go
index cb73409b..512d2550 100644
--- a/backend/internal/service/wire.go
+++ b/backend/internal/service/wire.go
@@ -87,6 +87,7 @@ var ProviderSet = wire.NewSet(
NewAccountService,
NewProxyService,
NewRedeemService,
+ NewPromoService,
NewUsageService,
NewDashboardService,
ProvidePricingService,
diff --git a/backend/migrations/033_add_promo_codes.sql b/backend/migrations/033_add_promo_codes.sql
new file mode 100644
index 00000000..7f6ae9a0
--- /dev/null
+++ b/backend/migrations/033_add_promo_codes.sql
@@ -0,0 +1,34 @@
+-- 创建注册优惠码表
+CREATE TABLE IF NOT EXISTS promo_codes (
+ id BIGSERIAL PRIMARY KEY,
+ code VARCHAR(32) NOT NULL UNIQUE,
+ bonus_amount DECIMAL(20,8) NOT NULL DEFAULT 0,
+ max_uses INT NOT NULL DEFAULT 0,
+ used_count INT NOT NULL DEFAULT 0,
+ status VARCHAR(20) NOT NULL DEFAULT 'active',
+ expires_at TIMESTAMPTZ DEFAULT NULL,
+ notes TEXT DEFAULT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+-- 创建优惠码使用记录表
+CREATE TABLE IF NOT EXISTS promo_code_usages (
+ id BIGSERIAL PRIMARY KEY,
+ promo_code_id BIGINT NOT NULL REFERENCES promo_codes(id) ON DELETE CASCADE,
+ user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ bonus_amount DECIMAL(20,8) NOT NULL,
+ used_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ UNIQUE(promo_code_id, user_id)
+);
+
+-- 索引
+CREATE INDEX IF NOT EXISTS idx_promo_codes_status ON promo_codes(status);
+CREATE INDEX IF NOT EXISTS idx_promo_codes_expires_at ON promo_codes(expires_at);
+CREATE INDEX IF NOT EXISTS idx_promo_code_usages_promo_code_id ON promo_code_usages(promo_code_id);
+CREATE INDEX IF NOT EXISTS idx_promo_code_usages_user_id ON promo_code_usages(user_id);
+
+COMMENT ON TABLE promo_codes IS '注册优惠码';
+COMMENT ON TABLE promo_code_usages IS '优惠码使用记录';
+COMMENT ON COLUMN promo_codes.max_uses IS '最大使用次数,0表示无限制';
+COMMENT ON COLUMN promo_codes.status IS '状态: active, disabled';
diff --git a/config.yaml b/config.yaml
index f43c9c19..54b591f3 100644
--- a/config.yaml
+++ b/config.yaml
@@ -154,9 +154,9 @@ gateway:
# Stream keepalive interval (seconds), 0=disable
# 流式 keepalive 间隔(秒),0=禁用
stream_keepalive_interval: 10
- # SSE max line size in bytes (default: 10MB)
- # SSE 单行最大字节数(默认 10MB)
- max_line_size: 10485760
+ # SSE max line size in bytes (default: 40MB)
+ # SSE 单行最大字节数(默认 40MB)
+ max_line_size: 41943040
# Log upstream error response body summary (safe/truncated; does not log request content)
# 记录上游错误响应体摘要(安全/截断;不记录请求内容)
log_upstream_error_body: false
diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml
index 936f0ea4..87ff3148 100644
--- a/deploy/config.example.yaml
+++ b/deploy/config.example.yaml
@@ -154,9 +154,9 @@ gateway:
# Stream keepalive interval (seconds), 0=disable
# 流式 keepalive 间隔(秒),0=禁用
stream_keepalive_interval: 10
- # SSE max line size in bytes (default: 10MB)
- # SSE 单行最大字节数(默认 10MB)
- max_line_size: 10485760
+ # SSE max line size in bytes (default: 40MB)
+ # SSE 单行最大字节数(默认 40MB)
+ max_line_size: 41943040
# Log upstream error response body summary (safe/truncated; does not log request content)
# 记录上游错误响应体摘要(安全/截断;不记录请求内容)
log_upstream_error_body: false
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 00000000..e7e1288d
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,5304 @@
+{
+ "name": "sub2api-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "sub2api-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "@lobehub/icons": "^4.0.2",
+ "@vueuse/core": "^10.7.0",
+ "axios": "^1.6.2",
+ "chart.js": "^4.4.1",
+ "driver.js": "^1.4.0",
+ "file-saver": "^2.0.5",
+ "pinia": "^2.1.7",
+ "vue": "^3.4.0",
+ "vue-chartjs": "^5.3.0",
+ "vue-i18n": "^9.14.5",
+ "vue-router": "^4.2.5",
+ "xlsx": "^0.18.5"
+ },
+ "devDependencies": {
+ "@types/file-saver": "^2.0.7",
+ "@types/mdx": "^2.0.13",
+ "@types/node": "^20.10.5",
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
+ "@typescript-eslint/parser": "^7.18.0",
+ "@vitejs/plugin-vue": "^5.2.3",
+ "autoprefixer": "^10.4.16",
+ "eslint": "^8.57.0",
+ "eslint-plugin-vue": "^9.25.0",
+ "postcss": "^8.4.32",
+ "tailwindcss": "^3.4.0",
+ "typescript": "~5.6.0",
+ "vite": "^5.0.10",
+ "vite-plugin-checker": "^0.9.1",
+ "vue-tsc": "^2.2.0"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.0.2.tgz",
+ "integrity": "sha512-7KDVIigtqlamOLtJ0hbjECX/sDGDaJXsM/KHala8I/1E4lpl9RAO585kbVvh/k1rIrFAV6JeGkXmdWyYj9XvuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache/node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/css": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz",
+ "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.13.5",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/serialize/node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/serialize/node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@intlify/core-base": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+ "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/message-compiler": "9.14.5",
+ "@intlify/shared": "9.14.5"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/message-compiler": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+ "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/shared": "9.14.5",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/shared": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+ "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
+ "node_modules/@lobehub/icons": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-4.0.2.tgz",
+ "integrity": "sha512-mYFEXXt7Z8iY8yLP5cDVctUPqlZUHWi5qzQCJiC646p7uiXhtpn93sRab/5pey+CYDh6BbRU6lhwiURu/SU5IA==",
+ "license": "MIT",
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "antd-style": "^4.1.0",
+ "lucide-react": "^0.469.0",
+ "polished": "^4.3.1"
+ },
+ "peerDependencies": {
+ "@lobehub/ui": "^4.3.3",
+ "antd": "^6.1.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rc-component/util": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.7.0.tgz",
+ "integrity": "sha512-tIvIGj4Vl6fsZFvWSkYw9sAfiCKUXMyhVz6kpKyZbwyZyRPqv2vxYZROdaO1VB4gqTNvUZFXh6i3APUiterw5g==",
+ "license": "MIT",
+ "dependencies": {
+ "is-mobile": "^5.0.0",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
+ "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
+ "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
+ "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
+ "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
+ "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
+ "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
+ "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
+ "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
+ "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
+ "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
+ "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
+ "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
+ "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
+ "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
+ "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
+ "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
+ "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
+ "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
+ "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
+ "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
+ "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
+ "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
+ "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/file-saver": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
+ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mdx": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
+ "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.27",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
+ "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/type-utils": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
+ "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/source-map": "2.4.15"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz",
+ "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@volar/typescript": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz",
+ "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.15",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz",
+ "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@vue/shared": "3.5.26",
+ "entities": "^7.0.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz",
+ "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.26",
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz",
+ "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@vue/compiler-core": "3.5.26",
+ "@vue/compiler-dom": "3.5.26",
+ "@vue/compiler-ssr": "3.5.26",
+ "@vue/shared": "3.5.26",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz",
+ "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.26",
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/compiler-vue2": {
+ "version": "2.7.16",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+ "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "de-indent": "^1.0.2",
+ "he": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/language-core": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz",
+ "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.15",
+ "@vue/compiler-dom": "^3.5.0",
+ "@vue/compiler-vue2": "^2.7.16",
+ "@vue/shared": "^3.5.0",
+ "alien-signals": "^1.0.3",
+ "minimatch": "^9.0.3",
+ "muggle-string": "^0.4.1",
+ "path-browserify": "^1.0.1"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz",
+ "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz",
+ "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.26",
+ "@vue/shared": "3.5.26"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz",
+ "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.26",
+ "@vue/runtime-core": "3.5.26",
+ "@vue/shared": "3.5.26",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz",
+ "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.26",
+ "@vue/shared": "3.5.26"
+ },
+ "peerDependencies": {
+ "vue": "3.5.26"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz",
+ "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
+ "license": "MIT"
+ },
+ "node_modules/@vueuse/core": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
+ "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.20",
+ "@vueuse/metadata": "10.11.1",
+ "@vueuse/shared": "10.11.1",
+ "vue-demi": ">=0.14.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
+ "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
+ "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+ "license": "MIT",
+ "dependencies": {
+ "vue-demi": ">=0.14.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/adler-32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
+ "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/alien-signals": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
+ "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/antd-style": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/antd-style/-/antd-style-4.1.0.tgz",
+ "integrity": "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/cssinjs": "^2.0.0",
+ "@babel/runtime": "^7.24.1",
+ "@emotion/cache": "^11.11.0",
+ "@emotion/css": "^11.11.2",
+ "@emotion/react": "^11.11.4",
+ "@emotion/serialize": "^1.1.3",
+ "@emotion/utils": "^1.2.1",
+ "use-merge-value": "^1.2.0"
+ },
+ "peerDependencies": {
+ "antd": ">=6.0.0",
+ "react": ">=18"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.23",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
+ "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1",
+ "caniuse-lite": "^1.0.30001760",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.14",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
+ "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001763",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz",
+ "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/cfb": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
+ "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "crc-32": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/codepage": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
+ "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/de-indent": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/driver.js": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.4.0.tgz",
+ "integrity": "sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==",
+ "license": "MIT"
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/entities": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz",
+ "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-vue": {
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz",
+ "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.3",
+ "vue-eslint-parser": "^9.4.3",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT"
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/frac": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+ "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-mobile": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
+ "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lucide-react": {
+ "version": "0.469.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz",
+ "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/muggle-string": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+ "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/polished": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
+ "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.55.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
+ "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.55.1",
+ "@rollup/rollup-android-arm64": "4.55.1",
+ "@rollup/rollup-darwin-arm64": "4.55.1",
+ "@rollup/rollup-darwin-x64": "4.55.1",
+ "@rollup/rollup-freebsd-arm64": "4.55.1",
+ "@rollup/rollup-freebsd-x64": "4.55.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.55.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.55.1",
+ "@rollup/rollup-linux-arm64-musl": "4.55.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.55.1",
+ "@rollup/rollup-linux-loong64-musl": "4.55.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.55.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.55.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.55.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.55.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-gnu": "4.55.1",
+ "@rollup/rollup-linux-x64-musl": "4.55.1",
+ "@rollup/rollup-openbsd-x64": "4.55.1",
+ "@rollup/rollup-openharmony-arm64": "4.55.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.55.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.55.1",
+ "@rollup/rollup-win32-x64-gnu": "4.55.1",
+ "@rollup/rollup-win32-x64-msvc": "4.55.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ssf": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
+ "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "frac": "~1.1.2"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
+ "license": "MIT"
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-merge-value": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz",
+ "integrity": "sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16.x"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-checker": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.9.3.tgz",
+ "integrity": "sha512-Tf7QBjeBtG7q11zG0lvoF38/2AVUzzhMNu+Wk+mcsJ00Rk/FpJ4rmUviVJpzWkagbU13cGXvKpt7CMiqtxVTbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "chokidar": "^4.0.3",
+ "npm-run-path": "^6.0.0",
+ "picocolors": "^1.1.1",
+ "picomatch": "^4.0.2",
+ "strip-ansi": "^7.1.0",
+ "tiny-invariant": "^1.3.3",
+ "tinyglobby": "^0.2.13",
+ "vscode-uri": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "peerDependencies": {
+ "@biomejs/biome": ">=1.7",
+ "eslint": ">=7",
+ "meow": "^13.2.0",
+ "optionator": "^0.9.4",
+ "stylelint": ">=16",
+ "typescript": "*",
+ "vite": ">=2.0.0",
+ "vls": "*",
+ "vti": "*",
+ "vue-tsc": "~2.2.10"
+ },
+ "peerDependenciesMeta": {
+ "@biomejs/biome": {
+ "optional": true
+ },
+ "eslint": {
+ "optional": true
+ },
+ "meow": {
+ "optional": true
+ },
+ "optionator": {
+ "optional": true
+ },
+ "stylelint": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ },
+ "vls": {
+ "optional": true
+ },
+ "vti": {
+ "optional": true
+ },
+ "vue-tsc": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vue": {
+ "version": "3.5.26",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
+ "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.26",
+ "@vue/compiler-sfc": "3.5.26",
+ "@vue/runtime-dom": "3.5.26",
+ "@vue/server-renderer": "3.5.26",
+ "@vue/shared": "3.5.26"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-chartjs": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz",
+ "integrity": "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "vue": "^3.0.0-0 || ^2.7.0"
+ }
+ },
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+ "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
+ "esquery": "^1.4.0",
+ "lodash": "^4.17.21",
+ "semver": "^7.3.6"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
+ "node_modules/vue-i18n": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+ "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+ "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "9.14.5",
+ "@intlify/shared": "9.14.5",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+ "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/vue-tsc": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz",
+ "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/typescript": "2.4.15",
+ "@vue/language-core": "2.2.12"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wmf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+ "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
+ "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/xlsx": {
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
+ "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "cfb": "~1.2.1",
+ "codepage": "~1.15.0",
+ "crc-32": "~1.2.1",
+ "ssf": "~0.11.2",
+ "wmf": "~1.0.1",
+ "word": "~0.3.0"
+ },
+ "bin": {
+ "xlsx": "bin/xlsx.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/frontend/src/api/admin/index.ts b/frontend/src/api/admin/index.ts
index ea12f6d2..c90017a8 100644
--- a/frontend/src/api/admin/index.ts
+++ b/frontend/src/api/admin/index.ts
@@ -9,6 +9,7 @@ import groupsAPI from './groups'
import accountsAPI from './accounts'
import proxiesAPI from './proxies'
import redeemAPI from './redeem'
+import promoAPI from './promo'
import settingsAPI from './settings'
import systemAPI from './system'
import subscriptionsAPI from './subscriptions'
@@ -27,6 +28,7 @@ export const adminAPI = {
accounts: accountsAPI,
proxies: proxiesAPI,
redeem: redeemAPI,
+ promo: promoAPI,
settings: settingsAPI,
system: systemAPI,
subscriptions: subscriptionsAPI,
@@ -43,6 +45,7 @@ export {
accountsAPI,
proxiesAPI,
redeemAPI,
+ promoAPI,
settingsAPI,
systemAPI,
subscriptionsAPI,
diff --git a/frontend/src/api/admin/promo.ts b/frontend/src/api/admin/promo.ts
new file mode 100644
index 00000000..6a8c4559
--- /dev/null
+++ b/frontend/src/api/admin/promo.ts
@@ -0,0 +1,69 @@
+/**
+ * Admin Promo Codes API endpoints
+ */
+
+import { apiClient } from '../client'
+import type {
+ PromoCode,
+ PromoCodeUsage,
+ CreatePromoCodeRequest,
+ UpdatePromoCodeRequest,
+ BasePaginationResponse
+} from '@/types'
+
+export async function list(
+ page: number = 1,
+ pageSize: number = 20,
+ filters?: {
+ status?: string
+ search?: string
+ }
+): Promise
+ {{ usage.user?.email || t('admin.promo.userPrefix', { id: usage.user_id }) }}
+
+ {{ formatDateTime(usage.used_at) }}
+ {{ value }}
+
+
+ {{ promoValidation.message }} +
+