From d2fc14fb97028d9c895ae09829155358b24ad0de Mon Sep 17 00:00:00 2001 From: long Date: Sat, 10 Jan 2026 13:14:35 +0800 Subject: [PATCH] =?UTF-8?q?=20=20feat:=20=E5=AE=9E=E7=8E=B0=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E4=BC=98=E6=83=A0=E7=A0=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持创建/编辑/删除优惠码,设置赠送金额和使用限制 - 注册页面实时验证优惠码并显示赠送金额 - 支持 URL 参数自动填充 (?promo=CODE) - 添加优惠码验证接口速率限制 - 使用数据库行锁防止并发超限 - 新增后台优惠码管理页面,支持复制注册链接 --- backend/cmd/jwtgen/main.go | 2 +- backend/cmd/server/wire_gen.go | 17 +- backend/ent/account_query.go | 37 + backend/ent/accountgroup_query.go | 37 + backend/ent/apikey_query.go | 37 + backend/ent/client.go | 370 +- backend/ent/ent.go | 4 + backend/ent/generate.go | 3 +- backend/ent/group_query.go | 37 + backend/ent/hook/hook.go | 24 + backend/ent/intercept/intercept.go | 60 + backend/ent/migrate/schema.go | 86 + backend/ent/mutation.go | 1711 +++++- backend/ent/predicate/predicate.go | 6 + backend/ent/promocode.go | 228 + backend/ent/promocode/promocode.go | 165 + backend/ent/promocode/where.go | 594 ++ backend/ent/promocode_create.go | 1081 ++++ backend/ent/promocode_delete.go | 88 + backend/ent/promocode_query.go | 643 ++ backend/ent/promocode_update.go | 745 +++ backend/ent/promocodeusage.go | 187 + backend/ent/promocodeusage/promocodeusage.go | 125 + backend/ent/promocodeusage/where.go | 257 + backend/ent/promocodeusage_create.go | 696 +++ backend/ent/promocodeusage_delete.go | 88 + backend/ent/promocodeusage_query.go | 718 +++ backend/ent/promocodeusage_update.go | 510 ++ backend/ent/proxy_query.go | 37 + backend/ent/redeemcode_query.go | 37 + backend/ent/runtime/runtime.go | 56 + backend/ent/schema/promo_code.go | 87 + backend/ent/schema/promo_code_usage.go | 66 + backend/ent/schema/user.go | 1 + backend/ent/setting_query.go | 37 + backend/ent/tx.go | 6 + backend/ent/usagelog_query.go | 37 + backend/ent/user.go | 20 +- backend/ent/user/user.go | 30 + backend/ent/user/where.go | 23 + backend/ent/user_create.go | 32 + backend/ent/user_query.go | 113 +- backend/ent/user_update.go | 163 + backend/ent/userallowedgroup_query.go | 37 + backend/ent/userattributedefinition_query.go | 37 + backend/ent/userattributevalue_query.go | 37 + backend/ent/usersubscription_query.go | 37 + .../internal/handler/admin/promo_handler.go | 209 + backend/internal/handler/auth_handler.go | 83 +- backend/internal/handler/dto/mappers.go | 32 + backend/internal/handler/dto/types.go | 25 + backend/internal/handler/handler.go | 1 + backend/internal/handler/wire.go | 3 + backend/internal/middleware/rate_limiter.go | 60 + .../internal/repository/promo_code_repo.go | 273 + backend/internal/repository/wire.go | 1 + backend/internal/server/http.go | 4 +- backend/internal/server/router.go | 7 +- backend/internal/server/routes/admin.go | 15 + backend/internal/server/routes/auth.go | 14 +- backend/internal/service/auth_service.go | 22 +- backend/internal/service/domain_constants.go | 6 + backend/internal/service/promo_code.go | 73 + .../internal/service/promo_code_repository.go | 30 + backend/internal/service/promo_service.go | 256 + backend/internal/service/wire.go | 1 + backend/migrations/033_add_promo_codes.sql | 34 + frontend/package-lock.json | 5304 +++++++++++++++++ frontend/src/api/admin/index.ts | 3 + frontend/src/api/admin/promo.ts | 69 + frontend/src/api/auth.ts | 23 +- frontend/src/components/layout/AppSidebar.vue | 1 + frontend/src/i18n/locales/en.ts | 74 +- frontend/src/i18n/locales/zh.ts | 72 + frontend/src/router/index.ts | 12 + frontend/src/types/index.ts | 42 + frontend/src/views/admin/PromoCodesView.vue | 718 +++ frontend/src/views/auth/EmailVerifyView.vue | 5 +- frontend/src/views/auth/RegisterView.vue | 178 +- 79 files changed, 17045 insertions(+), 54 deletions(-) create mode 100644 backend/ent/promocode.go create mode 100644 backend/ent/promocode/promocode.go create mode 100644 backend/ent/promocode/where.go create mode 100644 backend/ent/promocode_create.go create mode 100644 backend/ent/promocode_delete.go create mode 100644 backend/ent/promocode_query.go create mode 100644 backend/ent/promocode_update.go create mode 100644 backend/ent/promocodeusage.go create mode 100644 backend/ent/promocodeusage/promocodeusage.go create mode 100644 backend/ent/promocodeusage/where.go create mode 100644 backend/ent/promocodeusage_create.go create mode 100644 backend/ent/promocodeusage_delete.go create mode 100644 backend/ent/promocodeusage_query.go create mode 100644 backend/ent/promocodeusage_update.go create mode 100644 backend/ent/schema/promo_code.go create mode 100644 backend/ent/schema/promo_code_usage.go create mode 100644 backend/internal/handler/admin/promo_handler.go create mode 100644 backend/internal/middleware/rate_limiter.go create mode 100644 backend/internal/repository/promo_code_repo.go create mode 100644 backend/internal/service/promo_code.go create mode 100644 backend/internal/service/promo_code_repository.go create mode 100644 backend/internal/service/promo_service.go create mode 100644 backend/migrations/033_add_promo_codes.sql create mode 100644 frontend/package-lock.json create mode 100644 frontend/src/api/admin/promo.ts create mode 100644 frontend/src/views/admin/PromoCodesView.vue 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 85bed3f3..30ea0fdb 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) + engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, 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/handler/admin/promo_handler.go b/backend/internal/handler/admin/promo_handler.go new file mode 100644 index 00000000..14914a81 --- /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/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/http.go b/backend/internal/server/http.go index a8740ecc..90955867 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 提供服务器层的依赖 @@ -30,6 +31,7 @@ func ProvideRouter( apiKeyAuth middleware2.APIKeyAuthMiddleware, apiKeyService *service.APIKeyService, subscriptionService *service.SubscriptionService, + redisClient *redis.Client, ) *gin.Engine { if cfg.Server.Mode == "release" { gin.SetMode(gin.ReleaseMode) @@ -47,7 +49,7 @@ func ProvideRouter( } } - return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, cfg) + return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, cfg, redisClient) } // ProvideHTTPServer 提供 HTTP 服务器 diff --git a/backend/internal/server/router.go b/backend/internal/server/router.go index 15a1b325..2c0852a4 100644 --- a/backend/internal/server/router.go +++ b/backend/internal/server/router.go @@ -9,6 +9,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/web" "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" ) // SetupRouter 配置路由器中间件和路由 @@ -21,6 +22,7 @@ func SetupRouter( apiKeyService *service.APIKeyService, subscriptionService *service.SubscriptionService, cfg *config.Config, + redisClient *redis.Client, ) *gin.Engine { // 应用中间件 r.Use(middleware2.Logger()) @@ -33,7 +35,7 @@ func SetupRouter( } // 注册路由 - registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, cfg) + registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, cfg, redisClient) return r } @@ -48,6 +50,7 @@ func registerRoutes( apiKeyService *service.APIKeyService, subscriptionService *service.SubscriptionService, cfg *config.Config, + redisClient *redis.Client, ) { // 通用路由(健康检查、状态等) routes.RegisterCommonRoutes(r) @@ -56,7 +59,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/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/domain_constants.go b/backend/internal/service/domain_constants.go index df34e167..9014670d 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/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/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/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> { + const { data } = await apiClient.get>('/admin/promo-codes', { + params: { page, page_size: pageSize, ...filters } + }) + return data +} + +export async function getById(id: number): Promise { + const { data } = await apiClient.get(`/admin/promo-codes/${id}`) + return data +} + +export async function create(request: CreatePromoCodeRequest): Promise { + const { data } = await apiClient.post('/admin/promo-codes', request) + return data +} + +export async function update(id: number, request: UpdatePromoCodeRequest): Promise { + const { data } = await apiClient.put(`/admin/promo-codes/${id}`, request) + return data +} + +export async function deleteCode(id: number): Promise<{ message: string }> { + const { data } = await apiClient.delete<{ message: string }>(`/admin/promo-codes/${id}`) + return data +} + +export async function getUsages( + id: number, + page: number = 1, + pageSize: number = 20 +): Promise> { + const { data } = await apiClient.get>( + `/admin/promo-codes/${id}/usages`, + { params: { page, page_size: pageSize } } + ) + return data +} + +const promoAPI = { + list, + getById, + create, + update, + delete: deleteCode, + getUsages +} + +export default promoAPI diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 9c5379f2..fddc23ef 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -113,6 +113,26 @@ export async function sendVerifyCode( return data } +/** + * Validate promo code response + */ +export interface ValidatePromoCodeResponse { + valid: boolean + bonus_amount?: number + error_code?: string + message?: string +} + +/** + * Validate promo code (public endpoint, no auth required) + * @param code - Promo code to validate + * @returns Validation result with bonus amount if valid + */ +export async function validatePromoCode(code: string): Promise { + const { data } = await apiClient.post('/auth/validate-promo-code', { code }) + return data +} + export const authAPI = { login, register, @@ -123,7 +143,8 @@ export const authAPI = { getAuthToken, clearAuthToken, getPublicSettings, - sendVerifyCode + sendVerifyCode, + validatePromoCode } export default authAPI diff --git a/frontend/src/components/layout/AppSidebar.vue b/frontend/src/components/layout/AppSidebar.vue index 791327a1..d8c91475 100644 --- a/frontend/src/components/layout/AppSidebar.vue +++ b/frontend/src/components/layout/AppSidebar.vue @@ -448,6 +448,7 @@ const adminNavItems = computed(() => { { path: '/admin/accounts', label: t('nav.accounts'), icon: GlobeIcon }, { path: '/admin/proxies', label: t('nav.proxies'), icon: ServerIcon }, { path: '/admin/redeem', label: t('nav.redeemCodes'), icon: TicketIcon, hideInSimpleMode: true }, + { path: '/admin/promo-codes', label: t('nav.promoCodes'), icon: GiftIcon, hideInSimpleMode: true }, { path: '/admin/usage', label: t('nav.usage'), icon: ChartIcon }, ] diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index e7d3a28d..c9633e38 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -145,7 +145,8 @@ export default { copiedToClipboard: 'Copied to clipboard', copyFailed: 'Failed to copy', contactSupport: 'Contact Support', - selectOption: 'Select an option', + optional: 'optional', + selectOption: 'Select an option', searchPlaceholder: 'Search...', noOptionsFound: 'No options found', noGroupsAvailable: 'No groups available', @@ -177,6 +178,7 @@ export default { accounts: 'Accounts', proxies: 'Proxies', redeemCodes: 'Redeem Codes', + promoCodes: 'Promo Codes', settings: 'Settings', myAccount: 'My Account', lightMode: 'Light Mode', @@ -229,6 +231,17 @@ export default { sendingCode: 'Sending...', clickToResend: 'Click to resend code', resendCode: 'Resend verification code', + promoCodeLabel: 'Promo Code', + promoCodePlaceholder: 'Enter promo code (optional)', + promoCodeValid: 'Valid! You will receive ${amount} bonus balance', + promoCodeInvalid: 'Invalid promo code', + promoCodeNotFound: 'Promo code not found', + promoCodeExpired: 'This promo code has expired', + promoCodeDisabled: 'This promo code is disabled', + promoCodeMaxUsed: 'This promo code has reached its usage limit', + promoCodeAlreadyUsed: 'You have already used this promo code', + promoCodeValidating: 'Promo code is being validated, please wait', + promoCodeInvalidCannotRegister: 'Invalid promo code. Please check and try again or clear the promo code field', linuxdo: { signIn: 'Continue with Linux.do', orContinue: 'or continue with email', @@ -1722,6 +1735,65 @@ export default { } }, + // Promo Codes + promo: { + title: 'Promo Code Management', + description: 'Create and manage registration promo codes', + createCode: 'Create Promo Code', + editCode: 'Edit Promo Code', + deleteCode: 'Delete Promo Code', + searchCodes: 'Search codes...', + allStatus: 'All Status', + columns: { + code: 'Code', + bonusAmount: 'Bonus Amount', + maxUses: 'Max Uses', + usedCount: 'Used', + usage: 'Usage', + status: 'Status', + expiresAt: 'Expires At', + createdAt: 'Created At', + actions: 'Actions' + }, + // Form labels (flat structure for template usage) + code: 'Promo Code', + autoGenerate: 'auto-generate if empty', + codePlaceholder: 'Enter promo code or leave empty', + bonusAmount: 'Bonus Amount ($)', + maxUses: 'Max Uses', + zeroUnlimited: '0 = unlimited', + expiresAt: 'Expires At', + notes: 'Notes', + notesPlaceholder: 'Optional notes for this code', + status: 'Status', + neverExpires: 'Never expires', + // Status labels + statusActive: 'Active', + statusDisabled: 'Disabled', + statusExpired: 'Expired', + statusMaxUsed: 'Used Up', + // Usage records + usageRecords: 'Usage Records', + viewUsages: 'View Usages', + noUsages: 'No usage records yet', + userPrefix: 'User #{id}', + copied: 'Copied!', + // Messages + noCodesYet: 'No promo codes yet', + createFirstCode: 'Create your first promo code to offer registration bonuses.', + codeCreated: 'Promo code created successfully', + codeUpdated: 'Promo code updated successfully', + codeDeleted: 'Promo code deleted successfully', + deleteCodeConfirm: 'Are you sure you want to delete this promo code? This action cannot be undone.', + copyRegisterLink: 'Copy register link', + registerLinkCopied: 'Register link copied to clipboard', + failedToLoad: 'Failed to load promo codes', + failedToCreate: 'Failed to create promo code', + failedToUpdate: 'Failed to update promo code', + failedToDelete: 'Failed to delete promo code', + failedToLoadUsages: 'Failed to load usage records' + }, + // Usage Records usage: { title: 'Usage Records', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index fc1e6fff..6571d0e5 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -142,6 +142,7 @@ export default { copiedToClipboard: '已复制到剪贴板', copyFailed: '复制失败', contactSupport: '联系客服', + optional: '可选', selectOption: '请选择', searchPlaceholder: '搜索...', noOptionsFound: '无匹配选项', @@ -175,6 +176,7 @@ export default { accounts: '账号管理', proxies: 'IP管理', redeemCodes: '兑换码', + promoCodes: '优惠码', settings: '系统设置', myAccount: '我的账户', lightMode: '浅色模式', @@ -227,6 +229,17 @@ export default { sendingCode: '发送中...', clickToResend: '点击重新发送验证码', resendCode: '重新发送验证码', + promoCodeLabel: '优惠码', + promoCodePlaceholder: '输入优惠码(可选)', + promoCodeValid: '有效!注册后将获得 ${amount} 赠送余额', + promoCodeInvalid: '无效的优惠码', + promoCodeNotFound: '优惠码不存在', + promoCodeExpired: '此优惠码已过期', + promoCodeDisabled: '此优惠码已被禁用', + promoCodeMaxUsed: '此优惠码已达到使用上限', + promoCodeAlreadyUsed: '您已使用过此优惠码', + promoCodeValidating: '优惠码正在验证中,请稍候', + promoCodeInvalidCannotRegister: '优惠码无效,请检查后重试或清空优惠码', linuxdo: { signIn: '使用 Linux.do 登录', orContinue: '或使用邮箱密码继续', @@ -1867,6 +1880,65 @@ export default { failedToDelete: '删除兑换码失败' }, + // Promo Codes + promo: { + title: '优惠码管理', + description: '创建和管理注册优惠码', + createCode: '创建优惠码', + editCode: '编辑优惠码', + deleteCode: '删除优惠码', + searchCodes: '搜索优惠码...', + allStatus: '全部状态', + columns: { + code: '优惠码', + bonusAmount: '赠送金额', + maxUses: '最大使用次数', + usedCount: '已使用', + usage: '使用量', + status: '状态', + expiresAt: '过期时间', + createdAt: '创建时间', + actions: '操作' + }, + // 表单标签(扁平结构便于模板使用) + code: '优惠码', + autoGenerate: '留空自动生成', + codePlaceholder: '输入优惠码或留空', + bonusAmount: '赠送金额 ($)', + maxUses: '最大使用次数', + zeroUnlimited: '0 = 无限制', + expiresAt: '过期时间', + notes: '备注', + notesPlaceholder: '可选备注信息', + status: '状态', + neverExpires: '永不过期', + // 状态标签 + statusActive: '启用', + statusDisabled: '禁用', + statusExpired: '已过期', + statusMaxUsed: '已用完', + // 使用记录 + usageRecords: '使用记录', + viewUsages: '查看使用记录', + noUsages: '暂无使用记录', + userPrefix: '用户 #{id}', + copied: '已复制!', + // 消息 + noCodesYet: '暂无优惠码', + createFirstCode: '创建您的第一个优惠码,为新用户提供注册奖励。', + codeCreated: '优惠码创建成功', + codeUpdated: '优惠码更新成功', + codeDeleted: '优惠码删除成功', + deleteCodeConfirm: '确定要删除此优惠码吗?此操作无法撤销。', + copyRegisterLink: '复制注册链接', + registerLinkCopied: '注册链接已复制到剪贴板', + failedToLoad: '加载优惠码失败', + failedToCreate: '创建优惠码失败', + failedToUpdate: '更新优惠码失败', + failedToDelete: '删除优惠码失败', + failedToLoadUsages: '加载使用记录失败' + }, + // Usage Records usage: { title: '使用记录', diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 238982ef..6886704d 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -244,6 +244,18 @@ const routes: RouteRecordRaw[] = [ descriptionKey: 'admin.redeem.description' } }, + { + path: '/admin/promo-codes', + name: 'AdminPromoCodes', + component: () => import('@/views/admin/PromoCodesView.vue'), + meta: { + requiresAuth: true, + requiresAdmin: true, + title: 'Promo Code Management', + titleKey: 'admin.promo.title', + descriptionKey: 'admin.promo.description' + } + }, { path: '/admin/settings', name: 'AdminSettings', diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index bc858c6a..3d1b17f6 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -50,6 +50,7 @@ export interface RegisterRequest { password: string verify_code?: string turnstile_token?: string + promo_code?: string } export interface SendVerifyCodeRequest { @@ -960,3 +961,44 @@ export interface UpdateUserAttributeRequest { export interface UserAttributeValuesMap { [attributeId: number]: string } + +// ==================== Promo Code Types ==================== + +export interface PromoCode { + id: number + code: string + bonus_amount: number + max_uses: number + used_count: number + status: 'active' | 'disabled' + expires_at: string | null + notes: string | null + created_at: string + updated_at: string +} + +export interface PromoCodeUsage { + id: number + promo_code_id: number + user_id: number + bonus_amount: number + used_at: string + user?: User +} + +export interface CreatePromoCodeRequest { + code?: string + bonus_amount: number + max_uses?: number + expires_at?: number | null + notes?: string +} + +export interface UpdatePromoCodeRequest { + code?: string + bonus_amount?: number + max_uses?: number + status?: 'active' | 'disabled' + expires_at?: number | null + notes?: string +} diff --git a/frontend/src/views/admin/PromoCodesView.vue b/frontend/src/views/admin/PromoCodesView.vue new file mode 100644 index 00000000..968728b2 --- /dev/null +++ b/frontend/src/views/admin/PromoCodesView.vue @@ -0,0 +1,718 @@ + + + diff --git a/frontend/src/views/auth/EmailVerifyView.vue b/frontend/src/views/auth/EmailVerifyView.vue index abb43cab..3b6b45ba 100644 --- a/frontend/src/views/auth/EmailVerifyView.vue +++ b/frontend/src/views/auth/EmailVerifyView.vue @@ -200,6 +200,7 @@ let countdownTimer: ReturnType | null = null const email = ref('') const password = ref('') const initialTurnstileToken = ref('') +const promoCode = ref('') const hasRegisterData = ref(false) // Public settings @@ -228,6 +229,7 @@ onMounted(async () => { email.value = registerData.email || '' password.value = registerData.password || '' initialTurnstileToken.value = registerData.turnstile_token || '' + promoCode.value = registerData.promo_code || '' hasRegisterData.value = !!(email.value && password.value) } catch { hasRegisterData.value = false @@ -381,7 +383,8 @@ async function handleVerify(): Promise { email: email.value, password: password.value, verify_code: verifyCode.value.trim(), - turnstile_token: initialTurnstileToken.value || undefined + turnstile_token: initialTurnstileToken.value || undefined, + promo_code: promoCode.value || undefined }) // Clear session data diff --git a/frontend/src/views/auth/RegisterView.vue b/frontend/src/views/auth/RegisterView.vue index e48120b5..bfdc08e8 100644 --- a/frontend/src/views/auth/RegisterView.vue +++ b/frontend/src/views/auth/RegisterView.vue @@ -95,6 +95,57 @@

+ +
+ +
+
+ +
+ + +
+ + + + +
+
+ +
+
+ +
+
+ + +
+ + + {{ t('auth.promoCodeValid', { amount: promoValidation.bonusAmount?.toFixed(2) }) }} + +
+

+ {{ promoValidation.message }} +

+
+
+