diff --git a/.github/audit-exceptions.yml b/.github/audit-exceptions.yml index b71422a78aef03c7ad77ae7d9ab7ed61b674c3b8..4e05aae66b079db68d499ca06eaa137381ba1aed 100644 --- a/.github/audit-exceptions.yml +++ b/.github/audit-exceptions.yml @@ -28,3 +28,10 @@ exceptions: mitigation: "No user-controlled template strings; plan to migrate to native JS alternatives" expires_on: "2026-07-02" owner: "security@your-domain" + - package: axios + advisory: "GHSA-3p68-rc4w-qgx5" + severity: critical + reason: "NO_PROXY bypass not exploitable; all API calls go to known endpoints via server-side proxy" + mitigation: "Proxy configuration not user-controlled; upgrade when axios releases fix" + expires_on: "2026-07-10" + owner: "security@your-domain" diff --git a/.gitignore b/.gitignore index 297c1d6f039c4a3877516e82d311da2dcb61b472..1a92ea3e641316b8bc2f88def04cee30ec1377d5 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,8 @@ deploy/docker-compose.override.yml .gocache/ vite.config.js docs/* +!docs/PAYMENT.md +!docs/PAYMENT_CN.md .serena/ .codex/ frontend/coverage/ diff --git a/README.md b/README.md index 2f73e92ae73eb776787ab9fa6926612c853d8191..3a56d089c17d75e23885afd56f6a41b843613f94 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot - **Smart Scheduling** - Intelligent account selection with sticky sessions - **Concurrency Control** - Per-user and per-account concurrency limits - **Rate Limiting** - Configurable request and token rate limits +- **Built-in Payment System** - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed ([Configuration Guide](docs/PAYMENT.md)) - **Admin Dashboard** - Web interface for monitoring and management -- **External System Integration** - Embed external systems (e.g. payment, ticketing) via iframe to extend the admin dashboard +- **External System Integration** - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard ## ❤️ Sponsors @@ -74,6 +75,17 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot silkapi Thanks to SilkAPI for sponsoring this project! SilkAPI is a relay service built on Sub2API, specializing in providing high-speed and stable Codex API relay. + + +ylscode +Thanks to YLS Code for sponsoring this project! YLS Code is dedicated to building secure enterprise-grade Coding Agent productivity services, offering stable and fast Codex / Claude / Gemini subscription services along with pay-as-you-go API options for flexible choices. Register now for a limited-time 3-day Codex trial bonus! + + + +AICodeMirror +Thanks to AICodeMirror for sponsoring this project! AICodeMirror provides official high-stability relay services for Claude Code / Codex / Gemini CLI, with enterprise-grade concurrency, fast invoicing, and 24/7 dedicated technical support. Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original price, with extra discounts on top-ups! AICodeMirror offers special benefits for sub2api users: register via this link to enjoy 20% off your first top-up, and enterprise customers can get up to 25% off! + + ## Ecosystem @@ -82,7 +94,7 @@ Community projects that extend or integrate with Sub2API: | Project | Description | Features | |---------|-------------|----------| -| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | Self-service payment system | Self-service top-up and subscription purchase; supports YiPay protocol, WeChat Pay, Alipay, Stripe; embeddable via iframe | +| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~Self-service payment system~~ | **Now Built-in** — Payment is now integrated into Sub2API, no separate deployment needed. See [Payment Configuration Guide](docs/PAYMENT.md) | | [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | Mobile admin console | Cross-platform app (iOS/Android/Web) for user management, account management, monitoring dashboard, and multi-backend switching; built with Expo + React Native | ## Tech Stack diff --git a/README_CN.md b/README_CN.md index a0c3fd4bf33889274b03209f42f971951ae53d77..c0e6492e567bd2ee2fca32e8e5f9da87eab85821 100644 --- a/README_CN.md +++ b/README_CN.md @@ -41,8 +41,9 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的 - **智能调度** - 智能账号选择,支持粘性会话 - **并发控制** - 用户级和账号级并发限制 - **速率限制** - 可配置的请求和 Token 速率限制 +- **内置支付系统** - 支持 EasyPay 易支付、支付宝官方、微信官方、Stripe,用户自助充值,无需独立部署支付服务([配置指南](docs/PAYMENT_CN.md)) - **管理后台** - Web 界面进行监控和管理 -- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如支付、工单等),扩展管理后台功能 +- **外部系统集成** - 支持通过 iframe 嵌入外部系统(如工单等),扩展管理后台功能 ## ❤️ 赞助商 @@ -73,6 +74,17 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的 silkapi 感谢 丝绸API 赞助了本项目! 丝绸API 是基于 Sub2API 搭建的中转服务,专注于提供 Codex 高速稳定API中转。 + + +ylscode +感谢 伊莉思Code 赞助了本项目! 伊莉思Code 致力于构建安全的企业级Coding Agent生产力服务,提供稳定快速的 Codex / Claude / Gemini 订阅服务与即用即付API多种方案灵活选择,限时注册赠送 3 天 Codex 试用福利! + + + +AICodeMirror +感谢 AICodeMirror 赞助了本项目!AICodeMirror 提供 Claude Code / Codex / Gemini CLI 官方高稳定性中转服务,企业级并发、快速开票、7×24 小时专属技术支持。Claude Code / Codex / Gemini 官方通道低至原价 38% / 2% / 9%,充值更享额外折扣!AICodeMirror 为 sub2api 用户提供专属福利:通过此链接注册,首次充值立享 8 折优惠,企业客户最高可享 75 折! + + ## 生态项目 @@ -81,7 +93,7 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅的 | 项目 | 说明 | 功能 | |------|------|------| -| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | 自助支付系统 | 用户自助充值、自助订阅购买;兼容易支付协议、微信官方支付、支付宝官方支付、Stripe;支持 iframe 嵌入管理后台 | +| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~自助支付系统~~ | **已内置** — 支付功能已集成到 Sub2API 中,无需独立部署。详见 [支付配置指南](docs/PAYMENT_CN.md) | | [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | 移动端管理控制台 | 跨平台应用(iOS/Android/Web),支持用户管理、账号管理、监控看板、多后端切换;基于 Expo + React Native 构建 | ## 技术栈 diff --git a/README_JA.md b/README_JA.md index bd69e06bf5918a0aaac60b50f727ab6f6ab50169..4605b87749bd4b13031061052204794b051b88c4 100644 --- a/README_JA.md +++ b/README_JA.md @@ -42,8 +42,9 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを - **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択 - **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限 - **レート制限** - 設定可能なリクエスト数およびトークンレート制限 +- **内蔵決済システム** - EasyPay、Alipay、WeChat Pay、Stripe に対応。ユーザーのセルフサービスチャージが可能で、別途決済サービスのデプロイは不要([設定ガイド](docs/PAYMENT.md)) - **管理ダッシュボード** - 監視・管理のための Web インターフェース -- **外部システム連携** - 外部システム(決済、チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能 +- **外部システム連携** - 外部システム(チケット管理など)を iframe 経由で管理ダッシュボードに埋め込み可能 ## ❤️ スポンサー @@ -73,6 +74,17 @@ Sub2API は、AI 製品のサブスクリプションから API クォータを silkapi SilkAPI のご支援に感謝します!SilkAPI は Sub2API をベースに構築された中継サービスで、高速かつ安定した Codex API 中継の提供に特化しています。 + + +ylscode +YLS Code のご支援に感謝します!YLS Code は安全なエンタープライズグレードの Coding Agent 生産性サービスの構築に取り組んでおり、安定かつ高速な Codex / Claude / Gemini サブスクリプションサービスと従量課金 API の柔軟なプランを提供しています。期間限定で新規登録者に 3 日間の Codex 試用特典をプレゼント中! + + + +AICodeMirror +AICodeMirror のご支援に感謝します!AICodeMirror は Claude Code / Codex / Gemini CLI の公式高安定性リレーサービスを提供しており、エンタープライズグレードの同時実行、迅速な請求書発行、24時間年中無休の専属テクニカルサポートを備えています。Claude Code / Codex / Gemini の公式チャネルを定価の 38% / 2% / 9% で利用可能、チャージ時にはさらに追加割引!AICodeMirror は sub2api ユーザー向けに特別特典を提供中:こちらのリンクから登録すると、初回チャージが 20% オフ、法人のお客様は最大 25% オフ! + + ## エコシステム @@ -81,7 +93,7 @@ Sub2API を拡張・統合するコミュニティプロジェクト: | プロジェクト | 説明 | 機能 | |---------|-------------|----------| -| [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) | セルフサービス決済システム | セルフサービスによるチャージおよびサブスクリプション購入。YiPay プロトコル、WeChat Pay、Alipay、Stripe 対応。iframe での埋め込み可能 | +| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~セルフサービス決済システム~~ | **内蔵済み** — 決済機能は Sub2API に統合されました。別途デプロイは不要です。[決済設定ガイド](docs/PAYMENT.md)をご参照ください | | [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | モバイル管理コンソール | ユーザー管理、アカウント管理、監視ダッシュボード、マルチバックエンド切り替えが可能なクロスプラットフォームアプリ(iOS/Android/Web)。Expo + React Native で構築 | ## 技術スタック diff --git a/assets/partners/logos/AICodeMirror.jpg b/assets/partners/logos/AICodeMirror.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c98b2238a28ab2ffaaa84a9aa6b9b4094cf03f9 Binary files /dev/null and b/assets/partners/logos/AICodeMirror.jpg differ diff --git a/assets/partners/logos/ylscode.png b/assets/partners/logos/ylscode.png new file mode 100644 index 0000000000000000000000000000000000000000..4d374f04c2ffea503d71e0022b2937000d206126 Binary files /dev/null and b/assets/partners/logos/ylscode.png differ diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index b6e5c2adfabb39b565076ddb6d77cdb9bfad8e3a..715965f3592988297893c9a86c43b3e55633f3bd 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.110 +0.1.111 diff --git a/backend/cmd/server/wire.go b/backend/cmd/server/wire.go index 7fc648ac3a6955aca76468b99b9146a9ccd2b20c..47f8f518ff19ec29d79477ee984efc948da05269 100644 --- a/backend/cmd/server/wire.go +++ b/backend/cmd/server/wire.go @@ -13,6 +13,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/handler" + "github.com/Wei-Shaw/sub2api/internal/payment" "github.com/Wei-Shaw/sub2api/internal/repository" "github.com/Wei-Shaw/sub2api/internal/server" "github.com/Wei-Shaw/sub2api/internal/server/middleware" @@ -41,6 +42,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { // Server layer ProviderSet server.ProviderSet, + // Payment providers + payment.ProvideRegistry, + payment.ProvideEncryptionKey, + payment.ProvideDefaultLoadBalancer, + service.ProvidePaymentConfigService, + service.ProvidePaymentOrderExpiryService, + // Privacy client factory for OpenAI training opt-out providePrivacyClientFactory, @@ -76,7 +84,6 @@ func provideCleanup( opsCleanup *service.OpsCleanupService, opsScheduledReport *service.OpsScheduledReportService, opsSystemLogSink *service.OpsSystemLogSink, - soraMediaCleanup *service.SoraMediaCleanupService, schedulerSnapshot *service.SchedulerSnapshotService, tokenRefresh *service.TokenRefreshService, accountExpiry *service.AccountExpiryService, @@ -95,6 +102,7 @@ func provideCleanup( openAIGateway *service.OpenAIGatewayService, scheduledTestRunner *service.ScheduledTestRunnerService, backupSvc *service.BackupService, + paymentOrderExpiry *service.PaymentOrderExpiryService, ) func() { return func() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -125,12 +133,6 @@ func provideCleanup( } return nil }}, - {"SoraMediaCleanupService", func() error { - if soraMediaCleanup != nil { - soraMediaCleanup.Stop() - } - return nil - }}, {"OpsAlertEvaluatorService", func() error { if opsAlertEvaluator != nil { opsAlertEvaluator.Stop() @@ -237,6 +239,12 @@ func provideCleanup( } return nil }}, + {"PaymentOrderExpiryService", func() error { + if paymentOrderExpiry != nil { + paymentOrderExpiry.Stop() + } + return nil + }}, } infraSteps := []cleanupStep{ diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index fdc5c6acdb792e38e2d4400c272ce6a6fa2b0903..c288a289169515ab329cbac86e6943593ba60232 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -12,6 +12,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/handler" "github.com/Wei-Shaw/sub2api/internal/handler/admin" + "github.com/Wei-Shaw/sub2api/internal/payment" "github.com/Wei-Shaw/sub2api/internal/repository" "github.com/Wei-Shaw/sub2api/internal/server" "github.com/Wei-Shaw/sub2api/internal/server/middleware" @@ -72,6 +73,15 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator, billingCache) redeemCache := repository.NewRedeemCache(redisClient) redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator) + registry := payment.ProvideRegistry() + encryptionKey, err := payment.ProvideEncryptionKey(configConfig) + if err != nil { + return nil, err + } + defaultLoadBalancer := payment.ProvideDefaultLoadBalancer(client, encryptionKey) + paymentConfigService := service.ProvidePaymentConfigService(client, settingRepository, encryptionKey) + paymentService := service.NewPaymentService(client, registry, defaultLoadBalancer, redeemService, subscriptionService, paymentConfigService, userRepository, groupRepository) + paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService) secretEncryptor, err := repository.NewAESEncryptor(configConfig) if err != nil { return nil, err @@ -100,7 +110,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { } dashboardAggregationService := service.ProvideDashboardAggregationService(dashboardAggregationRepository, timingWheelService, configConfig) dashboardHandler := admin.NewDashboardHandler(dashboardService, dashboardAggregationService) - schedulerCache := repository.NewSchedulerCache(redisClient) + schedulerCache := repository.ProvideSchedulerCache(redisClient, configConfig) accountRepository := repository.NewAccountRepository(client, db, schedulerCache) proxyRepository := repository.NewProxyRepository(client, db) proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig) @@ -183,7 +193,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig) opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository) opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink) - settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService) + settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, paymentConfigService, paymentService) opsHandler := admin.NewOpsHandler(opsService) updateCache := repository.NewUpdateCache(redisClient) gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig) @@ -211,7 +221,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository) scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService) channelHandler := admin.NewChannelHandler(channelService, billingService) - adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler) + adminPaymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService) + adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, adminPaymentHandler) usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig) userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient) userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig) @@ -219,9 +230,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, configConfig) handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo) totpHandler := handler.NewTotpHandler(totpService) + handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService, channelService) + paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry) idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig) idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig) - handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, idempotencyCoordinator, idempotencyCleanupService) + handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, idempotencyCoordinator, idempotencyCleanupService) jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService) adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService) apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig) @@ -236,7 +249,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { accountExpiryService := service.ProvideAccountExpiryService(accountRepository) subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository) scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, rateLimitService, configConfig) - v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService) + v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService) application := &Application{ Server: httpServer, Cleanup: v, @@ -289,6 +302,7 @@ func provideCleanup( openAIGateway *service.OpenAIGatewayService, scheduledTestRunner *service.ScheduledTestRunnerService, backupSvc *service.BackupService, + paymentOrderExpiry *service.PaymentOrderExpiryService, ) func() { return func() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -424,6 +438,12 @@ func provideCleanup( } return nil }}, + {"PaymentOrderExpiryService", func() error { + if paymentOrderExpiry != nil { + paymentOrderExpiry.Stop() + } + return nil + }}, } infraSteps := []cleanupStep{ diff --git a/backend/cmd/server/wire_gen_test.go b/backend/cmd/server/wire_gen_test.go index 6e4561c9943373846cd9512ed8901aafcc1d0328..a6e0551a382e35e462c73373899f5f713da6e962 100644 --- a/backend/cmd/server/wire_gen_test.go +++ b/backend/cmd/server/wire_gen_test.go @@ -75,6 +75,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) { nil, // openAIGateway nil, // scheduledTestRunner nil, // backupSvc + nil, // paymentOrderExpiry ) require.NotPanics(t, func() { diff --git a/backend/ent/client.go b/backend/ent/client.go index 4129d6c5fefddc17f5b97de9fc68506dd673902a..e52e015ad8b1fe576a57f6298562a5619ab3e4a8 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -23,12 +23,16 @@ import ( "github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule" "github.com/Wei-Shaw/sub2api/ent/group" "github.com/Wei-Shaw/sub2api/ent/idempotencyrecord" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" "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/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/subscriptionplan" "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" @@ -62,6 +66,12 @@ type Client struct { Group *GroupClient // IdempotencyRecord is the client for interacting with the IdempotencyRecord builders. IdempotencyRecord *IdempotencyRecordClient + // PaymentAuditLog is the client for interacting with the PaymentAuditLog builders. + PaymentAuditLog *PaymentAuditLogClient + // PaymentOrder is the client for interacting with the PaymentOrder builders. + PaymentOrder *PaymentOrderClient + // PaymentProviderInstance is the client for interacting with the PaymentProviderInstance builders. + PaymentProviderInstance *PaymentProviderInstanceClient // PromoCode is the client for interacting with the PromoCode builders. PromoCode *PromoCodeClient // PromoCodeUsage is the client for interacting with the PromoCodeUsage builders. @@ -74,6 +84,8 @@ type Client struct { SecuritySecret *SecuritySecretClient // Setting is the client for interacting with the Setting builders. Setting *SettingClient + // SubscriptionPlan is the client for interacting with the SubscriptionPlan builders. + SubscriptionPlan *SubscriptionPlanClient // TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders. TLSFingerprintProfile *TLSFingerprintProfileClient // UsageCleanupTask is the client for interacting with the UsageCleanupTask builders. @@ -109,12 +121,16 @@ func (c *Client) init() { c.ErrorPassthroughRule = NewErrorPassthroughRuleClient(c.config) c.Group = NewGroupClient(c.config) c.IdempotencyRecord = NewIdempotencyRecordClient(c.config) + c.PaymentAuditLog = NewPaymentAuditLogClient(c.config) + c.PaymentOrder = NewPaymentOrderClient(c.config) + c.PaymentProviderInstance = NewPaymentProviderInstanceClient(c.config) c.PromoCode = NewPromoCodeClient(c.config) c.PromoCodeUsage = NewPromoCodeUsageClient(c.config) c.Proxy = NewProxyClient(c.config) c.RedeemCode = NewRedeemCodeClient(c.config) c.SecuritySecret = NewSecuritySecretClient(c.config) c.Setting = NewSettingClient(c.config) + c.SubscriptionPlan = NewSubscriptionPlanClient(c.config) c.TLSFingerprintProfile = NewTLSFingerprintProfileClient(c.config) c.UsageCleanupTask = NewUsageCleanupTaskClient(c.config) c.UsageLog = NewUsageLogClient(c.config) @@ -223,12 +239,16 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg), Group: NewGroupClient(cfg), IdempotencyRecord: NewIdempotencyRecordClient(cfg), + PaymentAuditLog: NewPaymentAuditLogClient(cfg), + PaymentOrder: NewPaymentOrderClient(cfg), + PaymentProviderInstance: NewPaymentProviderInstanceClient(cfg), PromoCode: NewPromoCodeClient(cfg), PromoCodeUsage: NewPromoCodeUsageClient(cfg), Proxy: NewProxyClient(cfg), RedeemCode: NewRedeemCodeClient(cfg), SecuritySecret: NewSecuritySecretClient(cfg), Setting: NewSettingClient(cfg), + SubscriptionPlan: NewSubscriptionPlanClient(cfg), TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg), UsageCleanupTask: NewUsageCleanupTaskClient(cfg), UsageLog: NewUsageLogClient(cfg), @@ -264,12 +284,16 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg), Group: NewGroupClient(cfg), IdempotencyRecord: NewIdempotencyRecordClient(cfg), + PaymentAuditLog: NewPaymentAuditLogClient(cfg), + PaymentOrder: NewPaymentOrderClient(cfg), + PaymentProviderInstance: NewPaymentProviderInstanceClient(cfg), PromoCode: NewPromoCodeClient(cfg), PromoCodeUsage: NewPromoCodeUsageClient(cfg), Proxy: NewProxyClient(cfg), RedeemCode: NewRedeemCodeClient(cfg), SecuritySecret: NewSecuritySecretClient(cfg), Setting: NewSettingClient(cfg), + SubscriptionPlan: NewSubscriptionPlanClient(cfg), TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg), UsageCleanupTask: NewUsageCleanupTaskClient(cfg), UsageLog: NewUsageLogClient(cfg), @@ -308,8 +332,9 @@ func (c *Client) Close() error { func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead, - c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode, - c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, + c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PaymentAuditLog, + c.PaymentOrder, c.PaymentProviderInstance, c.PromoCode, c.PromoCodeUsage, + c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription, @@ -323,8 +348,9 @@ func (c *Client) Use(hooks ...Hook) { func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead, - c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode, - c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, + c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PaymentAuditLog, + c.PaymentOrder, c.PaymentProviderInstance, c.PromoCode, c.PromoCodeUsage, + c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue, c.UserSubscription, @@ -352,6 +378,12 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.Group.mutate(ctx, m) case *IdempotencyRecordMutation: return c.IdempotencyRecord.mutate(ctx, m) + case *PaymentAuditLogMutation: + return c.PaymentAuditLog.mutate(ctx, m) + case *PaymentOrderMutation: + return c.PaymentOrder.mutate(ctx, m) + case *PaymentProviderInstanceMutation: + return c.PaymentProviderInstance.mutate(ctx, m) case *PromoCodeMutation: return c.PromoCode.mutate(ctx, m) case *PromoCodeUsageMutation: @@ -364,6 +396,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.SecuritySecret.mutate(ctx, m) case *SettingMutation: return c.Setting.mutate(ctx, m) + case *SubscriptionPlanMutation: + return c.SubscriptionPlan.mutate(ctx, m) case *TLSFingerprintProfileMutation: return c.TLSFingerprintProfile.mutate(ctx, m) case *UsageCleanupTaskMutation: @@ -1726,6 +1760,421 @@ func (c *IdempotencyRecordClient) mutate(ctx context.Context, m *IdempotencyReco } } +// PaymentAuditLogClient is a client for the PaymentAuditLog schema. +type PaymentAuditLogClient struct { + config +} + +// NewPaymentAuditLogClient returns a client for the PaymentAuditLog from the given config. +func NewPaymentAuditLogClient(c config) *PaymentAuditLogClient { + return &PaymentAuditLogClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `paymentauditlog.Hooks(f(g(h())))`. +func (c *PaymentAuditLogClient) Use(hooks ...Hook) { + c.hooks.PaymentAuditLog = append(c.hooks.PaymentAuditLog, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `paymentauditlog.Intercept(f(g(h())))`. +func (c *PaymentAuditLogClient) Intercept(interceptors ...Interceptor) { + c.inters.PaymentAuditLog = append(c.inters.PaymentAuditLog, interceptors...) +} + +// Create returns a builder for creating a PaymentAuditLog entity. +func (c *PaymentAuditLogClient) Create() *PaymentAuditLogCreate { + mutation := newPaymentAuditLogMutation(c.config, OpCreate) + return &PaymentAuditLogCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of PaymentAuditLog entities. +func (c *PaymentAuditLogClient) CreateBulk(builders ...*PaymentAuditLogCreate) *PaymentAuditLogCreateBulk { + return &PaymentAuditLogCreateBulk{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 *PaymentAuditLogClient) MapCreateBulk(slice any, setFunc func(*PaymentAuditLogCreate, int)) *PaymentAuditLogCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &PaymentAuditLogCreateBulk{err: fmt.Errorf("calling to PaymentAuditLogClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*PaymentAuditLogCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &PaymentAuditLogCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for PaymentAuditLog. +func (c *PaymentAuditLogClient) Update() *PaymentAuditLogUpdate { + mutation := newPaymentAuditLogMutation(c.config, OpUpdate) + return &PaymentAuditLogUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *PaymentAuditLogClient) UpdateOne(_m *PaymentAuditLog) *PaymentAuditLogUpdateOne { + mutation := newPaymentAuditLogMutation(c.config, OpUpdateOne, withPaymentAuditLog(_m)) + return &PaymentAuditLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *PaymentAuditLogClient) UpdateOneID(id int64) *PaymentAuditLogUpdateOne { + mutation := newPaymentAuditLogMutation(c.config, OpUpdateOne, withPaymentAuditLogID(id)) + return &PaymentAuditLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for PaymentAuditLog. +func (c *PaymentAuditLogClient) Delete() *PaymentAuditLogDelete { + mutation := newPaymentAuditLogMutation(c.config, OpDelete) + return &PaymentAuditLogDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *PaymentAuditLogClient) DeleteOne(_m *PaymentAuditLog) *PaymentAuditLogDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *PaymentAuditLogClient) DeleteOneID(id int64) *PaymentAuditLogDeleteOne { + builder := c.Delete().Where(paymentauditlog.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &PaymentAuditLogDeleteOne{builder} +} + +// Query returns a query builder for PaymentAuditLog. +func (c *PaymentAuditLogClient) Query() *PaymentAuditLogQuery { + return &PaymentAuditLogQuery{ + config: c.config, + ctx: &QueryContext{Type: TypePaymentAuditLog}, + inters: c.Interceptors(), + } +} + +// Get returns a PaymentAuditLog entity by its id. +func (c *PaymentAuditLogClient) Get(ctx context.Context, id int64) (*PaymentAuditLog, error) { + return c.Query().Where(paymentauditlog.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *PaymentAuditLogClient) GetX(ctx context.Context, id int64) *PaymentAuditLog { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *PaymentAuditLogClient) Hooks() []Hook { + return c.hooks.PaymentAuditLog +} + +// Interceptors returns the client interceptors. +func (c *PaymentAuditLogClient) Interceptors() []Interceptor { + return c.inters.PaymentAuditLog +} + +func (c *PaymentAuditLogClient) mutate(ctx context.Context, m *PaymentAuditLogMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&PaymentAuditLogCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&PaymentAuditLogUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&PaymentAuditLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&PaymentAuditLogDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown PaymentAuditLog mutation op: %q", m.Op()) + } +} + +// PaymentOrderClient is a client for the PaymentOrder schema. +type PaymentOrderClient struct { + config +} + +// NewPaymentOrderClient returns a client for the PaymentOrder from the given config. +func NewPaymentOrderClient(c config) *PaymentOrderClient { + return &PaymentOrderClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `paymentorder.Hooks(f(g(h())))`. +func (c *PaymentOrderClient) Use(hooks ...Hook) { + c.hooks.PaymentOrder = append(c.hooks.PaymentOrder, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `paymentorder.Intercept(f(g(h())))`. +func (c *PaymentOrderClient) Intercept(interceptors ...Interceptor) { + c.inters.PaymentOrder = append(c.inters.PaymentOrder, interceptors...) +} + +// Create returns a builder for creating a PaymentOrder entity. +func (c *PaymentOrderClient) Create() *PaymentOrderCreate { + mutation := newPaymentOrderMutation(c.config, OpCreate) + return &PaymentOrderCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of PaymentOrder entities. +func (c *PaymentOrderClient) CreateBulk(builders ...*PaymentOrderCreate) *PaymentOrderCreateBulk { + return &PaymentOrderCreateBulk{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 *PaymentOrderClient) MapCreateBulk(slice any, setFunc func(*PaymentOrderCreate, int)) *PaymentOrderCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &PaymentOrderCreateBulk{err: fmt.Errorf("calling to PaymentOrderClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*PaymentOrderCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &PaymentOrderCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for PaymentOrder. +func (c *PaymentOrderClient) Update() *PaymentOrderUpdate { + mutation := newPaymentOrderMutation(c.config, OpUpdate) + return &PaymentOrderUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *PaymentOrderClient) UpdateOne(_m *PaymentOrder) *PaymentOrderUpdateOne { + mutation := newPaymentOrderMutation(c.config, OpUpdateOne, withPaymentOrder(_m)) + return &PaymentOrderUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *PaymentOrderClient) UpdateOneID(id int64) *PaymentOrderUpdateOne { + mutation := newPaymentOrderMutation(c.config, OpUpdateOne, withPaymentOrderID(id)) + return &PaymentOrderUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for PaymentOrder. +func (c *PaymentOrderClient) Delete() *PaymentOrderDelete { + mutation := newPaymentOrderMutation(c.config, OpDelete) + return &PaymentOrderDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *PaymentOrderClient) DeleteOne(_m *PaymentOrder) *PaymentOrderDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *PaymentOrderClient) DeleteOneID(id int64) *PaymentOrderDeleteOne { + builder := c.Delete().Where(paymentorder.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &PaymentOrderDeleteOne{builder} +} + +// Query returns a query builder for PaymentOrder. +func (c *PaymentOrderClient) Query() *PaymentOrderQuery { + return &PaymentOrderQuery{ + config: c.config, + ctx: &QueryContext{Type: TypePaymentOrder}, + inters: c.Interceptors(), + } +} + +// Get returns a PaymentOrder entity by its id. +func (c *PaymentOrderClient) Get(ctx context.Context, id int64) (*PaymentOrder, error) { + return c.Query().Where(paymentorder.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *PaymentOrderClient) GetX(ctx context.Context, id int64) *PaymentOrder { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a PaymentOrder. +func (c *PaymentOrderClient) QueryUser(_m *PaymentOrder) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(paymentorder.Table, paymentorder.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, paymentorder.UserTable, paymentorder.UserColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *PaymentOrderClient) Hooks() []Hook { + return c.hooks.PaymentOrder +} + +// Interceptors returns the client interceptors. +func (c *PaymentOrderClient) Interceptors() []Interceptor { + return c.inters.PaymentOrder +} + +func (c *PaymentOrderClient) mutate(ctx context.Context, m *PaymentOrderMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&PaymentOrderCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&PaymentOrderUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&PaymentOrderUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&PaymentOrderDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown PaymentOrder mutation op: %q", m.Op()) + } +} + +// PaymentProviderInstanceClient is a client for the PaymentProviderInstance schema. +type PaymentProviderInstanceClient struct { + config +} + +// NewPaymentProviderInstanceClient returns a client for the PaymentProviderInstance from the given config. +func NewPaymentProviderInstanceClient(c config) *PaymentProviderInstanceClient { + return &PaymentProviderInstanceClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `paymentproviderinstance.Hooks(f(g(h())))`. +func (c *PaymentProviderInstanceClient) Use(hooks ...Hook) { + c.hooks.PaymentProviderInstance = append(c.hooks.PaymentProviderInstance, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `paymentproviderinstance.Intercept(f(g(h())))`. +func (c *PaymentProviderInstanceClient) Intercept(interceptors ...Interceptor) { + c.inters.PaymentProviderInstance = append(c.inters.PaymentProviderInstance, interceptors...) +} + +// Create returns a builder for creating a PaymentProviderInstance entity. +func (c *PaymentProviderInstanceClient) Create() *PaymentProviderInstanceCreate { + mutation := newPaymentProviderInstanceMutation(c.config, OpCreate) + return &PaymentProviderInstanceCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of PaymentProviderInstance entities. +func (c *PaymentProviderInstanceClient) CreateBulk(builders ...*PaymentProviderInstanceCreate) *PaymentProviderInstanceCreateBulk { + return &PaymentProviderInstanceCreateBulk{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 *PaymentProviderInstanceClient) MapCreateBulk(slice any, setFunc func(*PaymentProviderInstanceCreate, int)) *PaymentProviderInstanceCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &PaymentProviderInstanceCreateBulk{err: fmt.Errorf("calling to PaymentProviderInstanceClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*PaymentProviderInstanceCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &PaymentProviderInstanceCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for PaymentProviderInstance. +func (c *PaymentProviderInstanceClient) Update() *PaymentProviderInstanceUpdate { + mutation := newPaymentProviderInstanceMutation(c.config, OpUpdate) + return &PaymentProviderInstanceUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *PaymentProviderInstanceClient) UpdateOne(_m *PaymentProviderInstance) *PaymentProviderInstanceUpdateOne { + mutation := newPaymentProviderInstanceMutation(c.config, OpUpdateOne, withPaymentProviderInstance(_m)) + return &PaymentProviderInstanceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *PaymentProviderInstanceClient) UpdateOneID(id int64) *PaymentProviderInstanceUpdateOne { + mutation := newPaymentProviderInstanceMutation(c.config, OpUpdateOne, withPaymentProviderInstanceID(id)) + return &PaymentProviderInstanceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for PaymentProviderInstance. +func (c *PaymentProviderInstanceClient) Delete() *PaymentProviderInstanceDelete { + mutation := newPaymentProviderInstanceMutation(c.config, OpDelete) + return &PaymentProviderInstanceDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *PaymentProviderInstanceClient) DeleteOne(_m *PaymentProviderInstance) *PaymentProviderInstanceDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *PaymentProviderInstanceClient) DeleteOneID(id int64) *PaymentProviderInstanceDeleteOne { + builder := c.Delete().Where(paymentproviderinstance.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &PaymentProviderInstanceDeleteOne{builder} +} + +// Query returns a query builder for PaymentProviderInstance. +func (c *PaymentProviderInstanceClient) Query() *PaymentProviderInstanceQuery { + return &PaymentProviderInstanceQuery{ + config: c.config, + ctx: &QueryContext{Type: TypePaymentProviderInstance}, + inters: c.Interceptors(), + } +} + +// Get returns a PaymentProviderInstance entity by its id. +func (c *PaymentProviderInstanceClient) Get(ctx context.Context, id int64) (*PaymentProviderInstance, error) { + return c.Query().Where(paymentproviderinstance.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *PaymentProviderInstanceClient) GetX(ctx context.Context, id int64) *PaymentProviderInstance { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *PaymentProviderInstanceClient) Hooks() []Hook { + return c.hooks.PaymentProviderInstance +} + +// Interceptors returns the client interceptors. +func (c *PaymentProviderInstanceClient) Interceptors() []Interceptor { + return c.inters.PaymentProviderInstance +} + +func (c *PaymentProviderInstanceClient) mutate(ctx context.Context, m *PaymentProviderInstanceMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&PaymentProviderInstanceCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&PaymentProviderInstanceUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&PaymentProviderInstanceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&PaymentProviderInstanceDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown PaymentProviderInstance mutation op: %q", m.Op()) + } +} + // PromoCodeClient is a client for the PromoCode schema. type PromoCodeClient struct { config @@ -2622,6 +3071,139 @@ func (c *SettingClient) mutate(ctx context.Context, m *SettingMutation) (Value, } } +// SubscriptionPlanClient is a client for the SubscriptionPlan schema. +type SubscriptionPlanClient struct { + config +} + +// NewSubscriptionPlanClient returns a client for the SubscriptionPlan from the given config. +func NewSubscriptionPlanClient(c config) *SubscriptionPlanClient { + return &SubscriptionPlanClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `subscriptionplan.Hooks(f(g(h())))`. +func (c *SubscriptionPlanClient) Use(hooks ...Hook) { + c.hooks.SubscriptionPlan = append(c.hooks.SubscriptionPlan, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `subscriptionplan.Intercept(f(g(h())))`. +func (c *SubscriptionPlanClient) Intercept(interceptors ...Interceptor) { + c.inters.SubscriptionPlan = append(c.inters.SubscriptionPlan, interceptors...) +} + +// Create returns a builder for creating a SubscriptionPlan entity. +func (c *SubscriptionPlanClient) Create() *SubscriptionPlanCreate { + mutation := newSubscriptionPlanMutation(c.config, OpCreate) + return &SubscriptionPlanCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of SubscriptionPlan entities. +func (c *SubscriptionPlanClient) CreateBulk(builders ...*SubscriptionPlanCreate) *SubscriptionPlanCreateBulk { + return &SubscriptionPlanCreateBulk{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 *SubscriptionPlanClient) MapCreateBulk(slice any, setFunc func(*SubscriptionPlanCreate, int)) *SubscriptionPlanCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &SubscriptionPlanCreateBulk{err: fmt.Errorf("calling to SubscriptionPlanClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*SubscriptionPlanCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &SubscriptionPlanCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for SubscriptionPlan. +func (c *SubscriptionPlanClient) Update() *SubscriptionPlanUpdate { + mutation := newSubscriptionPlanMutation(c.config, OpUpdate) + return &SubscriptionPlanUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SubscriptionPlanClient) UpdateOne(_m *SubscriptionPlan) *SubscriptionPlanUpdateOne { + mutation := newSubscriptionPlanMutation(c.config, OpUpdateOne, withSubscriptionPlan(_m)) + return &SubscriptionPlanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *SubscriptionPlanClient) UpdateOneID(id int64) *SubscriptionPlanUpdateOne { + mutation := newSubscriptionPlanMutation(c.config, OpUpdateOne, withSubscriptionPlanID(id)) + return &SubscriptionPlanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for SubscriptionPlan. +func (c *SubscriptionPlanClient) Delete() *SubscriptionPlanDelete { + mutation := newSubscriptionPlanMutation(c.config, OpDelete) + return &SubscriptionPlanDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *SubscriptionPlanClient) DeleteOne(_m *SubscriptionPlan) *SubscriptionPlanDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *SubscriptionPlanClient) DeleteOneID(id int64) *SubscriptionPlanDeleteOne { + builder := c.Delete().Where(subscriptionplan.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &SubscriptionPlanDeleteOne{builder} +} + +// Query returns a query builder for SubscriptionPlan. +func (c *SubscriptionPlanClient) Query() *SubscriptionPlanQuery { + return &SubscriptionPlanQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeSubscriptionPlan}, + inters: c.Interceptors(), + } +} + +// Get returns a SubscriptionPlan entity by its id. +func (c *SubscriptionPlanClient) Get(ctx context.Context, id int64) (*SubscriptionPlan, error) { + return c.Query().Where(subscriptionplan.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *SubscriptionPlanClient) GetX(ctx context.Context, id int64) *SubscriptionPlan { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *SubscriptionPlanClient) Hooks() []Hook { + return c.hooks.SubscriptionPlan +} + +// Interceptors returns the client interceptors. +func (c *SubscriptionPlanClient) Interceptors() []Interceptor { + return c.inters.SubscriptionPlan +} + +func (c *SubscriptionPlanClient) mutate(ctx context.Context, m *SubscriptionPlanMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&SubscriptionPlanCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&SubscriptionPlanUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&SubscriptionPlanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&SubscriptionPlanDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown SubscriptionPlan mutation op: %q", m.Op()) + } +} + // TLSFingerprintProfileClient is a client for the TLSFingerprintProfile schema. type TLSFingerprintProfileClient struct { config @@ -3353,6 +3935,22 @@ func (c *UserClient) QueryPromoCodeUsages(_m *User) *PromoCodeUsageQuery { return query } +// QueryPaymentOrders queries the payment_orders edge of a User. +func (c *UserClient) QueryPaymentOrders(_m *User) *PaymentOrderQuery { + query := (&PaymentOrderClient{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(paymentorder.Table, paymentorder.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.PaymentOrdersTable, user.PaymentOrdersColumn), + ) + 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() @@ -4031,15 +4629,17 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription type ( hooks struct { APIKey, Account, AccountGroup, Announcement, AnnouncementRead, - ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage, - Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile, + ErrorPassthroughRule, Group, IdempotencyRecord, PaymentAuditLog, PaymentOrder, + PaymentProviderInstance, PromoCode, PromoCodeUsage, Proxy, RedeemCode, + SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition, UserAttributeValue, UserSubscription []ent.Hook } inters struct { APIKey, Account, AccountGroup, Announcement, AnnouncementRead, - ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage, - Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile, + ErrorPassthroughRule, Group, IdempotencyRecord, PaymentAuditLog, PaymentOrder, + PaymentProviderInstance, PromoCode, PromoCodeUsage, Proxy, RedeemCode, + SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition, UserAttributeValue, UserSubscription []ent.Interceptor } diff --git a/backend/ent/ent.go b/backend/ent/ent.go index bdeaed8a8ed40f3b7bfae1459c20105ef3f885ba..96ed5e03a9569a3b5a33922573aa9a2fbf090825 100644 --- a/backend/ent/ent.go +++ b/backend/ent/ent.go @@ -20,12 +20,16 @@ import ( "github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule" "github.com/Wei-Shaw/sub2api/ent/group" "github.com/Wei-Shaw/sub2api/ent/idempotencyrecord" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" "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/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/subscriptionplan" "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" @@ -102,12 +106,16 @@ func checkColumn(t, c string) error { errorpassthroughrule.Table: errorpassthroughrule.ValidColumn, group.Table: group.ValidColumn, idempotencyrecord.Table: idempotencyrecord.ValidColumn, + paymentauditlog.Table: paymentauditlog.ValidColumn, + paymentorder.Table: paymentorder.ValidColumn, + paymentproviderinstance.Table: paymentproviderinstance.ValidColumn, promocode.Table: promocode.ValidColumn, promocodeusage.Table: promocodeusage.ValidColumn, proxy.Table: proxy.ValidColumn, redeemcode.Table: redeemcode.ValidColumn, securitysecret.Table: securitysecret.ValidColumn, setting.Table: setting.ValidColumn, + subscriptionplan.Table: subscriptionplan.ValidColumn, tlsfingerprintprofile.Table: tlsfingerprintprofile.ValidColumn, usagecleanuptask.Table: usagecleanuptask.ValidColumn, usagelog.Table: usagelog.ValidColumn, diff --git a/backend/ent/group.go b/backend/ent/group.go index b15ac15dd923161a5880ed6132ea226ab9a5b909..f10b50c325e5b7cc507df510b9c222737aa1bc9c 100644 --- a/backend/ent/group.go +++ b/backend/ent/group.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/Wei-Shaw/sub2api/ent/group" + "github.com/Wei-Shaw/sub2api/internal/domain" ) // Group is the model entity for the Group schema. @@ -76,6 +77,8 @@ type Group struct { RequirePrivacySet bool `json:"require_privacy_set,omitempty"` // 默认映射模型 ID,当账号级映射找不到时使用此值 DefaultMappedModel string `json:"default_mapped_model,omitempty"` + // OpenAI Messages 调度模型配置:按 Claude 系列/精确模型映射到目标 GPT 模型 + MessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the GroupQuery when eager-loading is set. Edges GroupEdges `json:"edges"` @@ -182,7 +185,7 @@ func (*Group) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case group.FieldModelRouting, group.FieldSupportedModelScopes: + case group.FieldModelRouting, group.FieldSupportedModelScopes, group.FieldMessagesDispatchModelConfig: values[i] = new([]byte) case group.FieldIsExclusive, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet: values[i] = new(sql.NullBool) @@ -403,6 +406,14 @@ func (_m *Group) assignValues(columns []string, values []any) error { } else if value.Valid { _m.DefaultMappedModel = value.String } + case group.FieldMessagesDispatchModelConfig: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field messages_dispatch_model_config", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &_m.MessagesDispatchModelConfig); err != nil { + return fmt.Errorf("unmarshal field messages_dispatch_model_config: %w", err) + } + } default: _m.selectValues.Set(columns[i], values[i]) } @@ -585,6 +596,9 @@ func (_m *Group) String() string { builder.WriteString(", ") builder.WriteString("default_mapped_model=") builder.WriteString(_m.DefaultMappedModel) + builder.WriteString(", ") + builder.WriteString("messages_dispatch_model_config=") + builder.WriteString(fmt.Sprintf("%v", _m.MessagesDispatchModelConfig)) builder.WriteByte(')') return builder.String() } diff --git a/backend/ent/group/group.go b/backend/ent/group/group.go index 21a7c2cb767d9cf5be157ecc93a51881126f76d5..b1371630dd3caf5ebb806f99dd62e1bd004612b6 100644 --- a/backend/ent/group/group.go +++ b/backend/ent/group/group.go @@ -8,6 +8,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/Wei-Shaw/sub2api/internal/domain" ) const ( @@ -73,6 +74,8 @@ const ( FieldRequirePrivacySet = "require_privacy_set" // FieldDefaultMappedModel holds the string denoting the default_mapped_model field in the database. FieldDefaultMappedModel = "default_mapped_model" + // FieldMessagesDispatchModelConfig holds the string denoting the messages_dispatch_model_config field in the database. + FieldMessagesDispatchModelConfig = "messages_dispatch_model_config" // EdgeAPIKeys holds the string denoting the api_keys edge name in mutations. EdgeAPIKeys = "api_keys" // EdgeRedeemCodes holds the string denoting the redeem_codes edge name in mutations. @@ -177,6 +180,7 @@ var Columns = []string{ FieldRequireOauthOnly, FieldRequirePrivacySet, FieldDefaultMappedModel, + FieldMessagesDispatchModelConfig, } var ( @@ -252,6 +256,8 @@ var ( DefaultDefaultMappedModel string // DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save. DefaultMappedModelValidator func(string) error + // DefaultMessagesDispatchModelConfig holds the default value on creation for the "messages_dispatch_model_config" field. + DefaultMessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig ) // OrderOption defines the ordering options for the Group queries. diff --git a/backend/ent/group_create.go b/backend/ent/group_create.go index a8c30b184ddc04336783e7f086ee0b24a6df21f9..f412fa4070c13875f006c492903e9f7f80ecb62e 100644 --- a/backend/ent/group_create.go +++ b/backend/ent/group_create.go @@ -18,6 +18,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/internal/domain" ) // GroupCreate is the builder for creating a Group entity. @@ -410,6 +411,20 @@ func (_c *GroupCreate) SetNillableDefaultMappedModel(v *string) *GroupCreate { return _c } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (_c *GroupCreate) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupCreate { + _c.mutation.SetMessagesDispatchModelConfig(v) + return _c +} + +// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil. +func (_c *GroupCreate) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupCreate { + if v != nil { + _c.SetMessagesDispatchModelConfig(*v) + } + return _c +} + // AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs. func (_c *GroupCreate) AddAPIKeyIDs(ids ...int64) *GroupCreate { _c.mutation.AddAPIKeyIDs(ids...) @@ -611,6 +626,10 @@ func (_c *GroupCreate) defaults() error { v := group.DefaultDefaultMappedModel _c.mutation.SetDefaultMappedModel(v) } + if _, ok := _c.mutation.MessagesDispatchModelConfig(); !ok { + v := group.DefaultMessagesDispatchModelConfig + _c.mutation.SetMessagesDispatchModelConfig(v) + } return nil } @@ -695,6 +714,9 @@ func (_c *GroupCreate) check() error { return &ValidationError{Name: "default_mapped_model", err: fmt.Errorf(`ent: validator failed for field "Group.default_mapped_model": %w`, err)} } } + if _, ok := _c.mutation.MessagesDispatchModelConfig(); !ok { + return &ValidationError{Name: "messages_dispatch_model_config", err: errors.New(`ent: missing required field "Group.messages_dispatch_model_config"`)} + } return nil } @@ -838,6 +860,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) { _spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value) _node.DefaultMappedModel = value } + if value, ok := _c.mutation.MessagesDispatchModelConfig(); ok { + _spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value) + _node.MessagesDispatchModelConfig = value + } if nodes := _c.mutation.APIKeysIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -1462,6 +1488,18 @@ func (u *GroupUpsert) UpdateDefaultMappedModel() *GroupUpsert { return u } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (u *GroupUpsert) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsert { + u.Set(group.FieldMessagesDispatchModelConfig, v) + return u +} + +// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create. +func (u *GroupUpsert) UpdateMessagesDispatchModelConfig() *GroupUpsert { + u.SetExcluded(group.FieldMessagesDispatchModelConfig) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create. // Using this option is equivalent to using: // @@ -2053,6 +2091,20 @@ func (u *GroupUpsertOne) UpdateDefaultMappedModel() *GroupUpsertOne { }) } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (u *GroupUpsertOne) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.SetMessagesDispatchModelConfig(v) + }) +} + +// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create. +func (u *GroupUpsertOne) UpdateMessagesDispatchModelConfig() *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.UpdateMessagesDispatchModelConfig() + }) +} + // Exec executes the query. func (u *GroupUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -2810,6 +2862,20 @@ func (u *GroupUpsertBulk) UpdateDefaultMappedModel() *GroupUpsertBulk { }) } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (u *GroupUpsertBulk) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.SetMessagesDispatchModelConfig(v) + }) +} + +// UpdateMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field to the value that was provided on create. +func (u *GroupUpsertBulk) UpdateMessagesDispatchModelConfig() *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.UpdateMessagesDispatchModelConfig() + }) +} + // Exec executes the query. func (u *GroupUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/backend/ent/group_update.go b/backend/ent/group_update.go index aa1a83d421eb7f897a55a6e9de8d0e9ba5174ba8..7b6d6193256a1baf00559edc0f2381bb1ba26a3a 100644 --- a/backend/ent/group_update.go +++ b/backend/ent/group_update.go @@ -20,6 +20,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/internal/domain" ) // GroupUpdate is the builder for updating Group entities. @@ -552,6 +553,20 @@ func (_u *GroupUpdate) SetNillableDefaultMappedModel(v *string) *GroupUpdate { return _u } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (_u *GroupUpdate) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpdate { + _u.mutation.SetMessagesDispatchModelConfig(v) + return _u +} + +// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil. +func (_u *GroupUpdate) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupUpdate { + if v != nil { + _u.SetMessagesDispatchModelConfig(*v) + } + return _u +} + // AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs. func (_u *GroupUpdate) AddAPIKeyIDs(ids ...int64) *GroupUpdate { _u.mutation.AddAPIKeyIDs(ids...) @@ -1012,6 +1027,9 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) { if value, ok := _u.mutation.DefaultMappedModel(); ok { _spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value) } + if value, ok := _u.mutation.MessagesDispatchModelConfig(); ok { + _spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value) + } if _u.mutation.APIKeysCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -1843,6 +1861,20 @@ func (_u *GroupUpdateOne) SetNillableDefaultMappedModel(v *string) *GroupUpdateO return _u } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (_u *GroupUpdateOne) SetMessagesDispatchModelConfig(v domain.OpenAIMessagesDispatchModelConfig) *GroupUpdateOne { + _u.mutation.SetMessagesDispatchModelConfig(v) + return _u +} + +// SetNillableMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field if the given value is not nil. +func (_u *GroupUpdateOne) SetNillableMessagesDispatchModelConfig(v *domain.OpenAIMessagesDispatchModelConfig) *GroupUpdateOne { + if v != nil { + _u.SetMessagesDispatchModelConfig(*v) + } + return _u +} + // AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs. func (_u *GroupUpdateOne) AddAPIKeyIDs(ids ...int64) *GroupUpdateOne { _u.mutation.AddAPIKeyIDs(ids...) @@ -2333,6 +2365,9 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error) if value, ok := _u.mutation.DefaultMappedModel(); ok { _spec.SetField(group.FieldDefaultMappedModel, field.TypeString, value) } + if value, ok := _u.mutation.MessagesDispatchModelConfig(); ok { + _spec.SetField(group.FieldMessagesDispatchModelConfig, field.TypeJSON, value) + } if _u.mutation.APIKeysCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go index f6f7b4e9b4ff5cb40863c049458a60b9c2f3a8d8..199dacea0ecc8c8a06ac0e0789e5e968ec63b66e 100644 --- a/backend/ent/hook/hook.go +++ b/backend/ent/hook/hook.go @@ -105,6 +105,42 @@ func (f IdempotencyRecordFunc) Mutate(ctx context.Context, m ent.Mutation) (ent. return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.IdempotencyRecordMutation", m) } +// The PaymentAuditLogFunc type is an adapter to allow the use of ordinary +// function as PaymentAuditLog mutator. +type PaymentAuditLogFunc func(context.Context, *ent.PaymentAuditLogMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f PaymentAuditLogFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.PaymentAuditLogMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentAuditLogMutation", m) +} + +// The PaymentOrderFunc type is an adapter to allow the use of ordinary +// function as PaymentOrder mutator. +type PaymentOrderFunc func(context.Context, *ent.PaymentOrderMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f PaymentOrderFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.PaymentOrderMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentOrderMutation", m) +} + +// The PaymentProviderInstanceFunc type is an adapter to allow the use of ordinary +// function as PaymentProviderInstance mutator. +type PaymentProviderInstanceFunc func(context.Context, *ent.PaymentProviderInstanceMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f PaymentProviderInstanceFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.PaymentProviderInstanceMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentProviderInstanceMutation", 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) @@ -177,6 +213,18 @@ func (f SettingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m) } +// The SubscriptionPlanFunc type is an adapter to allow the use of ordinary +// function as SubscriptionPlan mutator. +type SubscriptionPlanFunc func(context.Context, *ent.SubscriptionPlanMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f SubscriptionPlanFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.SubscriptionPlanMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SubscriptionPlanMutation", m) +} + // The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary // function as TLSFingerprintProfile mutator. type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileMutation) (ent.Value, error) diff --git a/backend/ent/intercept/intercept.go b/backend/ent/intercept/intercept.go index 13169ca7151704a2873806fff570ca1c19d8f5c8..8d8320bbba5f6cdec4ba26ce89e5a81f532d30f9 100644 --- a/backend/ent/intercept/intercept.go +++ b/backend/ent/intercept/intercept.go @@ -16,6 +16,9 @@ import ( "github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule" "github.com/Wei-Shaw/sub2api/ent/group" "github.com/Wei-Shaw/sub2api/ent/idempotencyrecord" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" "github.com/Wei-Shaw/sub2api/ent/predicate" "github.com/Wei-Shaw/sub2api/ent/promocode" "github.com/Wei-Shaw/sub2api/ent/promocodeusage" @@ -23,6 +26,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/subscriptionplan" "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" @@ -305,6 +309,87 @@ func (f TraverseIdempotencyRecord) Traverse(ctx context.Context, q ent.Query) er return fmt.Errorf("unexpected query type %T. expect *ent.IdempotencyRecordQuery", q) } +// The PaymentAuditLogFunc type is an adapter to allow the use of ordinary function as a Querier. +type PaymentAuditLogFunc func(context.Context, *ent.PaymentAuditLogQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f PaymentAuditLogFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.PaymentAuditLogQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentAuditLogQuery", q) +} + +// The TraversePaymentAuditLog type is an adapter to allow the use of ordinary function as Traverser. +type TraversePaymentAuditLog func(context.Context, *ent.PaymentAuditLogQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraversePaymentAuditLog) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraversePaymentAuditLog) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.PaymentAuditLogQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.PaymentAuditLogQuery", q) +} + +// The PaymentOrderFunc type is an adapter to allow the use of ordinary function as a Querier. +type PaymentOrderFunc func(context.Context, *ent.PaymentOrderQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f PaymentOrderFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.PaymentOrderQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentOrderQuery", q) +} + +// The TraversePaymentOrder type is an adapter to allow the use of ordinary function as Traverser. +type TraversePaymentOrder func(context.Context, *ent.PaymentOrderQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraversePaymentOrder) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraversePaymentOrder) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.PaymentOrderQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.PaymentOrderQuery", q) +} + +// The PaymentProviderInstanceFunc type is an adapter to allow the use of ordinary function as a Querier. +type PaymentProviderInstanceFunc func(context.Context, *ent.PaymentProviderInstanceQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f PaymentProviderInstanceFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.PaymentProviderInstanceQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentProviderInstanceQuery", q) +} + +// The TraversePaymentProviderInstance type is an adapter to allow the use of ordinary function as Traverser. +type TraversePaymentProviderInstance func(context.Context, *ent.PaymentProviderInstanceQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraversePaymentProviderInstance) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraversePaymentProviderInstance) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.PaymentProviderInstanceQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.PaymentProviderInstanceQuery", 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) @@ -467,6 +552,33 @@ func (f TraverseSetting) Traverse(ctx context.Context, q ent.Query) error { return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q) } +// The SubscriptionPlanFunc type is an adapter to allow the use of ordinary function as a Querier. +type SubscriptionPlanFunc func(context.Context, *ent.SubscriptionPlanQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f SubscriptionPlanFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.SubscriptionPlanQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.SubscriptionPlanQuery", q) +} + +// The TraverseSubscriptionPlan type is an adapter to allow the use of ordinary function as Traverser. +type TraverseSubscriptionPlan func(context.Context, *ent.SubscriptionPlanQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraverseSubscriptionPlan) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraverseSubscriptionPlan) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.SubscriptionPlanQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.SubscriptionPlanQuery", q) +} + // The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary function as a Querier. type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileQuery) (ent.Value, error) @@ -702,6 +814,12 @@ func NewQuery(q ent.Query) (Query, error) { return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil case *ent.IdempotencyRecordQuery: return &query[*ent.IdempotencyRecordQuery, predicate.IdempotencyRecord, idempotencyrecord.OrderOption]{typ: ent.TypeIdempotencyRecord, tq: q}, nil + case *ent.PaymentAuditLogQuery: + return &query[*ent.PaymentAuditLogQuery, predicate.PaymentAuditLog, paymentauditlog.OrderOption]{typ: ent.TypePaymentAuditLog, tq: q}, nil + case *ent.PaymentOrderQuery: + return &query[*ent.PaymentOrderQuery, predicate.PaymentOrder, paymentorder.OrderOption]{typ: ent.TypePaymentOrder, tq: q}, nil + case *ent.PaymentProviderInstanceQuery: + return &query[*ent.PaymentProviderInstanceQuery, predicate.PaymentProviderInstance, paymentproviderinstance.OrderOption]{typ: ent.TypePaymentProviderInstance, tq: q}, nil case *ent.PromoCodeQuery: return &query[*ent.PromoCodeQuery, predicate.PromoCode, promocode.OrderOption]{typ: ent.TypePromoCode, tq: q}, nil case *ent.PromoCodeUsageQuery: @@ -714,6 +832,8 @@ func NewQuery(q ent.Query) (Query, error) { return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, tq: q}, nil case *ent.SettingQuery: return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil + case *ent.SubscriptionPlanQuery: + return &query[*ent.SubscriptionPlanQuery, predicate.SubscriptionPlan, subscriptionplan.OrderOption]{typ: ent.TypeSubscriptionPlan, tq: q}, nil case *ent.TLSFingerprintProfileQuery: return &query[*ent.TLSFingerprintProfileQuery, predicate.TLSFingerprintProfile, tlsfingerprintprofile.OrderOption]{typ: ent.TypeTLSFingerprintProfile, tq: q}, nil case *ent.UsageCleanupTaskQuery: diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 5400bf9319c0adf1233cdbd7c3edad36d7b743e6..e947b2e88253cd79eb6f9803d9a1b8b96ac2f63e 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -407,6 +407,7 @@ var ( {Name: "require_oauth_only", Type: field.TypeBool, Default: false}, {Name: "require_privacy_set", Type: field.TypeBool, Default: false}, {Name: "default_mapped_model", Type: field.TypeString, Size: 100, Default: ""}, + {Name: "messages_dispatch_model_config", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}}, } // GroupsTable holds the schema information for the "groups" table. GroupsTable = &schema.Table{ @@ -484,6 +485,158 @@ var ( }, }, } + // PaymentAuditLogsColumns holds the columns for the "payment_audit_logs" table. + PaymentAuditLogsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "order_id", Type: field.TypeString, Size: 64}, + {Name: "action", Type: field.TypeString, Size: 50}, + {Name: "detail", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}}, + {Name: "operator", Type: field.TypeString, Size: 100, Default: "system"}, + {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + } + // PaymentAuditLogsTable holds the schema information for the "payment_audit_logs" table. + PaymentAuditLogsTable = &schema.Table{ + Name: "payment_audit_logs", + Columns: PaymentAuditLogsColumns, + PrimaryKey: []*schema.Column{PaymentAuditLogsColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "paymentauditlog_order_id", + Unique: false, + Columns: []*schema.Column{PaymentAuditLogsColumns[1]}, + }, + }, + } + // PaymentOrdersColumns holds the columns for the "payment_orders" table. + PaymentOrdersColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "user_email", Type: field.TypeString, Size: 255}, + {Name: "user_name", Type: field.TypeString, Size: 100}, + {Name: "user_notes", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}}, + {Name: "pay_amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}}, + {Name: "fee_rate", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(10,4)"}}, + {Name: "recharge_code", Type: field.TypeString, Size: 64}, + {Name: "out_trade_no", Type: field.TypeString, Size: 64, Default: ""}, + {Name: "payment_type", Type: field.TypeString, Size: 30}, + {Name: "payment_trade_no", Type: field.TypeString, Size: 128}, + {Name: "pay_url", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "qr_code", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "qr_code_img", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "order_type", Type: field.TypeString, Size: 20, Default: "balance"}, + {Name: "plan_id", Type: field.TypeInt64, Nullable: true}, + {Name: "subscription_group_id", Type: field.TypeInt64, Nullable: true}, + {Name: "subscription_days", Type: field.TypeInt, Nullable: true}, + {Name: "provider_instance_id", Type: field.TypeString, Nullable: true, Size: 64}, + {Name: "status", Type: field.TypeString, Size: 30, Default: "PENDING"}, + {Name: "refund_amount", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,2)"}}, + {Name: "refund_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "refund_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "force_refund", Type: field.TypeBool, Default: false}, + {Name: "refund_requested_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "refund_request_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "refund_requested_by", Type: field.TypeString, Nullable: true, Size: 20}, + {Name: "expires_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "paid_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "completed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "failed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "failed_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "client_ip", Type: field.TypeString, Size: 50}, + {Name: "src_host", Type: field.TypeString, Size: 255}, + {Name: "src_url", 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"}}, + {Name: "user_id", Type: field.TypeInt64}, + } + // PaymentOrdersTable holds the schema information for the "payment_orders" table. + PaymentOrdersTable = &schema.Table{ + Name: "payment_orders", + Columns: PaymentOrdersColumns, + PrimaryKey: []*schema.Column{PaymentOrdersColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "payment_orders_users_payment_orders", + Columns: []*schema.Column{PaymentOrdersColumns[37]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "paymentorder_out_trade_no", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[8]}, + }, + { + Name: "paymentorder_user_id", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[37]}, + }, + { + Name: "paymentorder_status", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[19]}, + }, + { + Name: "paymentorder_expires_at", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[27]}, + }, + { + Name: "paymentorder_created_at", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[35]}, + }, + { + Name: "paymentorder_paid_at", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[28]}, + }, + { + Name: "paymentorder_payment_type_paid_at", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[9], PaymentOrdersColumns[28]}, + }, + { + Name: "paymentorder_order_type", + Unique: false, + Columns: []*schema.Column{PaymentOrdersColumns[14]}, + }, + }, + } + // PaymentProviderInstancesColumns holds the columns for the "payment_provider_instances" table. + PaymentProviderInstancesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "provider_key", Type: field.TypeString, Size: 30}, + {Name: "name", Type: field.TypeString, Size: 100, Default: ""}, + {Name: "config", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "supported_types", Type: field.TypeString, Size: 200, Default: ""}, + {Name: "enabled", Type: field.TypeBool, Default: true}, + {Name: "payment_mode", Type: field.TypeString, Size: 20, Default: ""}, + {Name: "sort_order", Type: field.TypeInt, Default: 0}, + {Name: "limits", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}}, + {Name: "refund_enabled", Type: field.TypeBool, Default: false}, + {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + } + // PaymentProviderInstancesTable holds the schema information for the "payment_provider_instances" table. + PaymentProviderInstancesTable = &schema.Table{ + Name: "payment_provider_instances", + Columns: PaymentProviderInstancesColumns, + PrimaryKey: []*schema.Column{PaymentProviderInstancesColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "paymentproviderinstance_provider_key", + Unique: false, + Columns: []*schema.Column{PaymentProviderInstancesColumns[1]}, + }, + { + Name: "paymentproviderinstance_enabled", + Unique: false, + Columns: []*schema.Column{PaymentProviderInstancesColumns[5]}, + }, + }, + } // PromoCodesColumns holds the columns for the "promo_codes" table. PromoCodesColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt64, Increment: true}, @@ -670,6 +823,41 @@ var ( Columns: SettingsColumns, PrimaryKey: []*schema.Column{SettingsColumns[0]}, } + // SubscriptionPlansColumns holds the columns for the "subscription_plans" table. + SubscriptionPlansColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "group_id", Type: field.TypeInt64}, + {Name: "name", Type: field.TypeString, Size: 100}, + {Name: "description", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}}, + {Name: "price", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}}, + {Name: "original_price", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,2)"}}, + {Name: "validity_days", Type: field.TypeInt, Default: 30}, + {Name: "validity_unit", Type: field.TypeString, Size: 10, Default: "day"}, + {Name: "features", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}}, + {Name: "product_name", Type: field.TypeString, Size: 100, Default: ""}, + {Name: "for_sale", Type: field.TypeBool, Default: true}, + {Name: "sort_order", Type: field.TypeInt, Default: 0}, + {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + } + // SubscriptionPlansTable holds the schema information for the "subscription_plans" table. + SubscriptionPlansTable = &schema.Table{ + Name: "subscription_plans", + Columns: SubscriptionPlansColumns, + PrimaryKey: []*schema.Column{SubscriptionPlansColumns[0]}, + Indexes: []*schema.Index{ + { + Name: "subscriptionplan_group_id", + Unique: false, + Columns: []*schema.Column{SubscriptionPlansColumns[1]}, + }, + { + Name: "subscriptionplan_for_sale", + Unique: false, + Columns: []*schema.Column{SubscriptionPlansColumns[10]}, + }, + }, + } // TLSFingerprintProfilesColumns holds the columns for the "tls_fingerprint_profiles" table. TLSFingerprintProfilesColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt64, Increment: true}, @@ -1127,12 +1315,16 @@ var ( ErrorPassthroughRulesTable, GroupsTable, IdempotencyRecordsTable, + PaymentAuditLogsTable, + PaymentOrdersTable, + PaymentProviderInstancesTable, PromoCodesTable, PromoCodeUsagesTable, ProxiesTable, RedeemCodesTable, SecuritySecretsTable, SettingsTable, + SubscriptionPlansTable, TLSFingerprintProfilesTable, UsageCleanupTasksTable, UsageLogsTable, @@ -1176,6 +1368,16 @@ func init() { IdempotencyRecordsTable.Annotation = &entsql.Annotation{ Table: "idempotency_records", } + PaymentAuditLogsTable.Annotation = &entsql.Annotation{ + Table: "payment_audit_logs", + } + PaymentOrdersTable.ForeignKeys[0].RefTable = UsersTable + PaymentOrdersTable.Annotation = &entsql.Annotation{ + Table: "payment_orders", + } + PaymentProviderInstancesTable.Annotation = &entsql.Annotation{ + Table: "payment_provider_instances", + } PromoCodesTable.Annotation = &entsql.Annotation{ Table: "promo_codes", } @@ -1198,6 +1400,9 @@ func init() { SettingsTable.Annotation = &entsql.Annotation{ Table: "settings", } + SubscriptionPlansTable.Annotation = &entsql.Annotation{ + Table: "subscription_plans", + } TLSFingerprintProfilesTable.Annotation = &entsql.Annotation{ Table: "tls_fingerprint_profiles", } diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index d206039af4714f5542a1001efc5867bcf806c58e..6b2fa838692468cba8ea65b624f57192665c2525 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -20,6 +20,9 @@ import ( "github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule" "github.com/Wei-Shaw/sub2api/ent/group" "github.com/Wei-Shaw/sub2api/ent/idempotencyrecord" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" "github.com/Wei-Shaw/sub2api/ent/predicate" "github.com/Wei-Shaw/sub2api/ent/promocode" "github.com/Wei-Shaw/sub2api/ent/promocodeusage" @@ -27,6 +30,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/subscriptionplan" "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" @@ -55,12 +59,16 @@ const ( TypeErrorPassthroughRule = "ErrorPassthroughRule" TypeGroup = "Group" TypeIdempotencyRecord = "IdempotencyRecord" + TypePaymentAuditLog = "PaymentAuditLog" + TypePaymentOrder = "PaymentOrder" + TypePaymentProviderInstance = "PaymentProviderInstance" TypePromoCode = "PromoCode" TypePromoCodeUsage = "PromoCodeUsage" TypeProxy = "Proxy" TypeRedeemCode = "RedeemCode" TypeSecuritySecret = "SecuritySecret" TypeSetting = "Setting" + TypeSubscriptionPlan = "SubscriptionPlan" TypeTLSFingerprintProfile = "TLSFingerprintProfile" TypeUsageCleanupTask = "UsageCleanupTask" TypeUsageLog = "UsageLog" @@ -8246,6 +8254,7 @@ type GroupMutation struct { require_oauth_only *bool require_privacy_set *bool default_mapped_model *string + messages_dispatch_model_config *domain.OpenAIMessagesDispatchModelConfig clearedFields map[string]struct{} api_keys map[int64]struct{} removedapi_keys map[int64]struct{} @@ -9798,6 +9807,42 @@ func (m *GroupMutation) ResetDefaultMappedModel() { m.default_mapped_model = nil } +// SetMessagesDispatchModelConfig sets the "messages_dispatch_model_config" field. +func (m *GroupMutation) SetMessagesDispatchModelConfig(damdmc domain.OpenAIMessagesDispatchModelConfig) { + m.messages_dispatch_model_config = &damdmc +} + +// MessagesDispatchModelConfig returns the value of the "messages_dispatch_model_config" field in the mutation. +func (m *GroupMutation) MessagesDispatchModelConfig() (r domain.OpenAIMessagesDispatchModelConfig, exists bool) { + v := m.messages_dispatch_model_config + if v == nil { + return + } + return *v, true +} + +// OldMessagesDispatchModelConfig returns the old "messages_dispatch_model_config" field's value of the Group entity. +// If the Group 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 *GroupMutation) OldMessagesDispatchModelConfig(ctx context.Context) (v domain.OpenAIMessagesDispatchModelConfig, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMessagesDispatchModelConfig is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMessagesDispatchModelConfig requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMessagesDispatchModelConfig: %w", err) + } + return oldValue.MessagesDispatchModelConfig, nil +} + +// ResetMessagesDispatchModelConfig resets all changes to the "messages_dispatch_model_config" field. +func (m *GroupMutation) ResetMessagesDispatchModelConfig() { + m.messages_dispatch_model_config = nil +} + // AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by ids. func (m *GroupMutation) AddAPIKeyIDs(ids ...int64) { if m.api_keys == nil { @@ -10156,7 +10201,7 @@ func (m *GroupMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *GroupMutation) Fields() []string { - fields := make([]string, 0, 29) + fields := make([]string, 0, 30) if m.created_at != nil { fields = append(fields, group.FieldCreatedAt) } @@ -10244,6 +10289,9 @@ func (m *GroupMutation) Fields() []string { if m.default_mapped_model != nil { fields = append(fields, group.FieldDefaultMappedModel) } + if m.messages_dispatch_model_config != nil { + fields = append(fields, group.FieldMessagesDispatchModelConfig) + } return fields } @@ -10310,6 +10358,8 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) { return m.RequirePrivacySet() case group.FieldDefaultMappedModel: return m.DefaultMappedModel() + case group.FieldMessagesDispatchModelConfig: + return m.MessagesDispatchModelConfig() } return nil, false } @@ -10377,6 +10427,8 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldRequirePrivacySet(ctx) case group.FieldDefaultMappedModel: return m.OldDefaultMappedModel(ctx) + case group.FieldMessagesDispatchModelConfig: + return m.OldMessagesDispatchModelConfig(ctx) } return nil, fmt.Errorf("unknown Group field %s", name) } @@ -10589,6 +10641,13 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error { } m.SetDefaultMappedModel(v) return nil + case group.FieldMessagesDispatchModelConfig: + v, ok := value.(domain.OpenAIMessagesDispatchModelConfig) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMessagesDispatchModelConfig(v) + return nil } return fmt.Errorf("unknown Group field %s", name) } @@ -10929,6 +10988,9 @@ func (m *GroupMutation) ResetField(name string) error { case group.FieldDefaultMappedModel: m.ResetDefaultMappedModel() return nil + case group.FieldMessagesDispatchModelConfig: + m.ResetMessagesDispatchModelConfig() + return nil } return fmt.Errorf("unknown Group field %s", name) } @@ -12129,44 +12191,34 @@ func (m *IdempotencyRecordMutation) ResetEdge(name string) error { return fmt.Errorf("unknown IdempotencyRecord edge %s", name) } -// PromoCodeMutation represents an operation that mutates the PromoCode nodes in the graph. -type PromoCodeMutation struct { +// PaymentAuditLogMutation represents an operation that mutates the PaymentAuditLog nodes in the graph. +type PaymentAuditLogMutation 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 + op Op + typ string + id *int64 + order_id *string + action *string + detail *string + operator *string + created_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*PaymentAuditLog, error) + predicates []predicate.PaymentAuditLog } -var _ ent.Mutation = (*PromoCodeMutation)(nil) +var _ ent.Mutation = (*PaymentAuditLogMutation)(nil) -// promocodeOption allows management of the mutation configuration using functional options. -type promocodeOption func(*PromoCodeMutation) +// paymentauditlogOption allows management of the mutation configuration using functional options. +type paymentauditlogOption func(*PaymentAuditLogMutation) -// newPromoCodeMutation creates new mutation for the PromoCode entity. -func newPromoCodeMutation(c config, op Op, opts ...promocodeOption) *PromoCodeMutation { - m := &PromoCodeMutation{ +// newPaymentAuditLogMutation creates new mutation for the PaymentAuditLog entity. +func newPaymentAuditLogMutation(c config, op Op, opts ...paymentauditlogOption) *PaymentAuditLogMutation { + m := &PaymentAuditLogMutation{ config: c, op: op, - typ: TypePromoCode, + typ: TypePaymentAuditLog, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -12175,20 +12227,20 @@ func newPromoCodeMutation(c config, op Op, opts ...promocodeOption) *PromoCodeMu return m } -// withPromoCodeID sets the ID field of the mutation. -func withPromoCodeID(id int64) promocodeOption { - return func(m *PromoCodeMutation) { +// withPaymentAuditLogID sets the ID field of the mutation. +func withPaymentAuditLogID(id int64) paymentauditlogOption { + return func(m *PaymentAuditLogMutation) { var ( err error once sync.Once - value *PromoCode + value *PaymentAuditLog ) - m.oldValue = func(ctx context.Context) (*PromoCode, error) { + m.oldValue = func(ctx context.Context) (*PaymentAuditLog, 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) + value, err = m.Client().PaymentAuditLog.Get(ctx, id) } }) return value, err @@ -12197,10 +12249,10 @@ func withPromoCodeID(id int64) promocodeOption { } } -// withPromoCode sets the old PromoCode of the mutation. -func withPromoCode(node *PromoCode) promocodeOption { - return func(m *PromoCodeMutation) { - m.oldValue = func(context.Context) (*PromoCode, error) { +// withPaymentAuditLog sets the old PaymentAuditLog of the mutation. +func withPaymentAuditLog(node *PaymentAuditLog) paymentauditlogOption { + return func(m *PaymentAuditLogMutation) { + m.oldValue = func(context.Context) (*PaymentAuditLog, error) { return node, nil } m.id = &node.ID @@ -12209,7 +12261,7 @@ func withPromoCode(node *PromoCode) promocodeOption { // 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 { +func (m PaymentAuditLogMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -12217,7 +12269,7 @@ func (m PromoCodeMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m PromoCodeMutation) Tx() (*Tx, error) { +func (m PaymentAuditLogMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("ent: mutation is not running in a transaction") } @@ -12228,7 +12280,7 @@ func (m PromoCodeMutation) Tx() (*Tx, error) { // 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) { +func (m *PaymentAuditLogMutation) ID() (id int64, exists bool) { if m.id == nil { return } @@ -12239,7 +12291,7 @@ func (m *PromoCodeMutation) ID() (id int64, exists bool) { // 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) { +func (m *PaymentAuditLogMutation) IDs(ctx context.Context) ([]int64, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -12248,1225 +12300,6471 @@ func (m *PromoCodeMutation) IDs(ctx context.Context) ([]int64, error) { } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().PromoCode.Query().Where(m.predicates...).IDs(ctx) + return m.Client().PaymentAuditLog.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 +// SetOrderID sets the "order_id" field. +func (m *PaymentAuditLogMutation) SetOrderID(s string) { + m.order_id = &s } -// Code returns the value of the "code" field in the mutation. -func (m *PromoCodeMutation) Code() (r string, exists bool) { - v := m.code +// OrderID returns the value of the "order_id" field in the mutation. +func (m *PaymentAuditLogMutation) OrderID() (r string, exists bool) { + v := m.order_id 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. +// OldOrderID returns the old "order_id" field's value of the PaymentAuditLog entity. +// If the PaymentAuditLog 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) { +func (m *PaymentAuditLogMutation) OldOrderID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCode is only allowed on UpdateOne operations") + return v, errors.New("OldOrderID 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") + return v, errors.New("OldOrderID 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 v, fmt.Errorf("querying old value for OldOrderID: %w", err) } - return oldValue.Code, nil + return oldValue.OrderID, nil } -// ResetCode resets all changes to the "code" field. -func (m *PromoCodeMutation) ResetCode() { - m.code = nil +// ResetOrderID resets all changes to the "order_id" field. +func (m *PaymentAuditLogMutation) ResetOrderID() { + m.order_id = nil } -// SetBonusAmount sets the "bonus_amount" field. -func (m *PromoCodeMutation) SetBonusAmount(f float64) { - m.bonus_amount = &f - m.addbonus_amount = nil +// SetAction sets the "action" field. +func (m *PaymentAuditLogMutation) SetAction(s string) { + m.action = &s } -// BonusAmount returns the value of the "bonus_amount" field in the mutation. -func (m *PromoCodeMutation) BonusAmount() (r float64, exists bool) { - v := m.bonus_amount +// Action returns the value of the "action" field in the mutation. +func (m *PaymentAuditLogMutation) Action() (r string, exists bool) { + v := m.action 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. +// OldAction returns the old "action" field's value of the PaymentAuditLog entity. +// If the PaymentAuditLog 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) { +func (m *PaymentAuditLogMutation) OldAction(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations") + return v, errors.New("OldAction 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") + return v, errors.New("OldAction 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, fmt.Errorf("querying old value for OldAction: %w", err) } - return *v, true + return oldValue.Action, nil } -// ResetBonusAmount resets all changes to the "bonus_amount" field. -func (m *PromoCodeMutation) ResetBonusAmount() { - m.bonus_amount = nil - m.addbonus_amount = nil +// ResetAction resets all changes to the "action" field. +func (m *PaymentAuditLogMutation) ResetAction() { + m.action = nil } -// SetMaxUses sets the "max_uses" field. -func (m *PromoCodeMutation) SetMaxUses(i int) { - m.max_uses = &i - m.addmax_uses = nil +// SetDetail sets the "detail" field. +func (m *PaymentAuditLogMutation) SetDetail(s string) { + m.detail = &s } -// MaxUses returns the value of the "max_uses" field in the mutation. -func (m *PromoCodeMutation) MaxUses() (r int, exists bool) { - v := m.max_uses +// Detail returns the value of the "detail" field in the mutation. +func (m *PaymentAuditLogMutation) Detail() (r string, exists bool) { + v := m.detail 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. +// OldDetail returns the old "detail" field's value of the PaymentAuditLog entity. +// If the PaymentAuditLog 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) { +func (m *PaymentAuditLogMutation) OldDetail(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldMaxUses is only allowed on UpdateOne operations") + return v, errors.New("OldDetail 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") + return v, errors.New("OldDetail 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, fmt.Errorf("querying old value for OldDetail: %w", err) } - return *v, true + return oldValue.Detail, nil } -// ResetMaxUses resets all changes to the "max_uses" field. -func (m *PromoCodeMutation) ResetMaxUses() { - m.max_uses = nil - m.addmax_uses = nil +// ResetDetail resets all changes to the "detail" field. +func (m *PaymentAuditLogMutation) ResetDetail() { + m.detail = nil } -// SetUsedCount sets the "used_count" field. -func (m *PromoCodeMutation) SetUsedCount(i int) { - m.used_count = &i - m.addused_count = nil +// SetOperator sets the "operator" field. +func (m *PaymentAuditLogMutation) SetOperator(s string) { + m.operator = &s } -// UsedCount returns the value of the "used_count" field in the mutation. -func (m *PromoCodeMutation) UsedCount() (r int, exists bool) { - v := m.used_count +// Operator returns the value of the "operator" field in the mutation. +func (m *PaymentAuditLogMutation) Operator() (r string, exists bool) { + v := m.operator 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. +// OldOperator returns the old "operator" field's value of the PaymentAuditLog entity. +// If the PaymentAuditLog 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) { +func (m *PaymentAuditLogMutation) OldOperator(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsedCount is only allowed on UpdateOne operations") + return v, errors.New("OldOperator 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") + return v, errors.New("OldOperator 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, fmt.Errorf("querying old value for OldOperator: %w", err) } - return *v, true + return oldValue.Operator, nil } -// ResetUsedCount resets all changes to the "used_count" field. -func (m *PromoCodeMutation) ResetUsedCount() { - m.used_count = nil - m.addused_count = nil +// ResetOperator resets all changes to the "operator" field. +func (m *PaymentAuditLogMutation) ResetOperator() { + m.operator = nil } -// SetStatus sets the "status" field. -func (m *PromoCodeMutation) SetStatus(s string) { - m.status = &s +// SetCreatedAt sets the "created_at" field. +func (m *PaymentAuditLogMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } -// Status returns the value of the "status" field in the mutation. -func (m *PromoCodeMutation) Status() (r string, exists bool) { - v := m.status +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *PaymentAuditLogMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at 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. +// OldCreatedAt returns the old "created_at" field's value of the PaymentAuditLog entity. +// If the PaymentAuditLog 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) { +func (m *PaymentAuditLogMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldStatus is only allowed on UpdateOne operations") + return v, errors.New("OldCreatedAt 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") + 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 OldStatus: %w", err) + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } - return oldValue.Status, nil + return oldValue.CreatedAt, nil } -// ResetStatus resets all changes to the "status" field. -func (m *PromoCodeMutation) ResetStatus() { - m.status = nil +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *PaymentAuditLogMutation) ResetCreatedAt() { + m.created_at = nil } -// SetExpiresAt sets the "expires_at" field. -func (m *PromoCodeMutation) SetExpiresAt(t time.Time) { - m.expires_at = &t +// Where appends a list predicates to the PaymentAuditLogMutation builder. +func (m *PaymentAuditLogMutation) Where(ps ...predicate.PaymentAuditLog) { + m.predicates = append(m.predicates, ps...) } -// 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 +// WhereP appends storage-level predicates to the PaymentAuditLogMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *PaymentAuditLogMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.PaymentAuditLog, len(ps)) + for i := range ps { + p[i] = ps[i] } - return *v, true + m.Where(p...) } -// 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 +// Op returns the operation name. +func (m *PaymentAuditLogMutation) Op() Op { + return m.op } -// ResetExpiresAt resets all changes to the "expires_at" field. -func (m *PromoCodeMutation) ResetExpiresAt() { - m.expires_at = nil - delete(m.clearedFields, promocode.FieldExpiresAt) +// SetOp allows setting the mutation operation. +func (m *PaymentAuditLogMutation) SetOp(op Op) { + m.op = op } -// SetNotes sets the "notes" field. -func (m *PromoCodeMutation) SetNotes(s string) { - m.notes = &s +// Type returns the node type of this mutation (PaymentAuditLog). +func (m *PaymentAuditLogMutation) Type() string { + return m.typ } -// 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 +// 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 *PaymentAuditLogMutation) Fields() []string { + fields := make([]string, 0, 5) + if m.order_id != nil { + fields = append(fields, paymentauditlog.FieldOrderID) } - 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.action != nil { + fields = append(fields, paymentauditlog.FieldAction) } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldNotes requires an ID field in the mutation") + if m.detail != nil { + fields = append(fields, paymentauditlog.FieldDetail) } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldNotes: %w", err) + if m.operator != nil { + fields = append(fields, paymentauditlog.FieldOperator) } - return oldValue.Notes, nil + if m.created_at != nil { + fields = append(fields, paymentauditlog.FieldCreatedAt) + } + return fields } -// ClearNotes clears the value of the "notes" field. -func (m *PromoCodeMutation) ClearNotes() { - m.notes = nil - m.clearedFields[promocode.FieldNotes] = struct{}{} +// 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 *PaymentAuditLogMutation) Field(name string) (ent.Value, bool) { + switch name { + case paymentauditlog.FieldOrderID: + return m.OrderID() + case paymentauditlog.FieldAction: + return m.Action() + case paymentauditlog.FieldDetail: + return m.Detail() + case paymentauditlog.FieldOperator: + return m.Operator() + case paymentauditlog.FieldCreatedAt: + return m.CreatedAt() + } + return nil, false } -// NotesCleared returns if the "notes" field was cleared in this mutation. -func (m *PromoCodeMutation) NotesCleared() bool { - _, ok := m.clearedFields[promocode.FieldNotes] - return ok +// 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 *PaymentAuditLogMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case paymentauditlog.FieldOrderID: + return m.OldOrderID(ctx) + case paymentauditlog.FieldAction: + return m.OldAction(ctx) + case paymentauditlog.FieldDetail: + return m.OldDetail(ctx) + case paymentauditlog.FieldOperator: + return m.OldOperator(ctx) + case paymentauditlog.FieldCreatedAt: + return m.OldCreatedAt(ctx) + } + return nil, fmt.Errorf("unknown PaymentAuditLog field %s", name) } -// ResetNotes resets all changes to the "notes" field. -func (m *PromoCodeMutation) ResetNotes() { - m.notes = nil - delete(m.clearedFields, promocode.FieldNotes) +// 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 *PaymentAuditLogMutation) SetField(name string, value ent.Value) error { + switch name { + case paymentauditlog.FieldOrderID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOrderID(v) + return nil + case paymentauditlog.FieldAction: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAction(v) + return nil + case paymentauditlog.FieldDetail: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDetail(v) + return nil + case paymentauditlog.FieldOperator: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOperator(v) + return nil + case paymentauditlog.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + } + return fmt.Errorf("unknown PaymentAuditLog field %s", name) } -// SetCreatedAt sets the "created_at" field. -func (m *PromoCodeMutation) SetCreatedAt(t time.Time) { - m.created_at = &t +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *PaymentAuditLogMutation) AddedFields() []string { + return nil } -// 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 +// 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 *PaymentAuditLogMutation) AddedField(name string) (ent.Value, bool) { + return nil, false } -// 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) +// 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 *PaymentAuditLogMutation) AddField(name string, value ent.Value) error { + switch name { } - return oldValue.CreatedAt, nil + return fmt.Errorf("unknown PaymentAuditLog numeric field %s", name) } -// ResetCreatedAt resets all changes to the "created_at" field. -func (m *PromoCodeMutation) ResetCreatedAt() { - m.created_at = nil +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *PaymentAuditLogMutation) ClearedFields() []string { + return nil } -// SetUpdatedAt sets the "updated_at" field. -func (m *PromoCodeMutation) SetUpdatedAt(t time.Time) { - m.updated_at = &t +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *PaymentAuditLogMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok } -// 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 +// 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 *PaymentAuditLogMutation) ClearField(name string) error { + return fmt.Errorf("unknown PaymentAuditLog nullable field %s", name) } -// 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) +// 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 *PaymentAuditLogMutation) ResetField(name string) error { + switch name { + case paymentauditlog.FieldOrderID: + m.ResetOrderID() + return nil + case paymentauditlog.FieldAction: + m.ResetAction() + return nil + case paymentauditlog.FieldDetail: + m.ResetDetail() + return nil + case paymentauditlog.FieldOperator: + m.ResetOperator() + return nil + case paymentauditlog.FieldCreatedAt: + m.ResetCreatedAt() + return nil } - return oldValue.UpdatedAt, nil + return fmt.Errorf("unknown PaymentAuditLog field %s", name) } -// ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *PromoCodeMutation) ResetUpdatedAt() { - m.updated_at = nil +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *PaymentAuditLogMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges } -// 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{}{} - } +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *PaymentAuditLogMutation) AddedIDs(name string) []ent.Value { + return nil } -// ClearUsageRecords clears the "usage_records" edge to the PromoCodeUsage entity. -func (m *PromoCodeMutation) ClearUsageRecords() { - m.clearedusage_records = true +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *PaymentAuditLogMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges } -// UsageRecordsCleared reports if the "usage_records" edge to the PromoCodeUsage entity was cleared. -func (m *PromoCodeMutation) UsageRecordsCleared() bool { - return m.clearedusage_records +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *PaymentAuditLogMutation) RemovedIDs(name string) []ent.Value { + return nil } -// 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{}{} - } +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *PaymentAuditLogMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges } -// 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 +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *PaymentAuditLogMutation) EdgeCleared(name string) bool { + return false } -// 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 +// 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 *PaymentAuditLogMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown PaymentAuditLog unique edge %s", name) } -// 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 +// 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 *PaymentAuditLogMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown PaymentAuditLog edge %s", name) } -// Where appends a list predicates to the PromoCodeMutation builder. -func (m *PromoCodeMutation) Where(ps ...predicate.PromoCode) { - m.predicates = append(m.predicates, ps...) +// PaymentOrderMutation represents an operation that mutates the PaymentOrder nodes in the graph. +type PaymentOrderMutation struct { + config + op Op + typ string + id *int64 + user_email *string + user_name *string + user_notes *string + amount *float64 + addamount *float64 + pay_amount *float64 + addpay_amount *float64 + fee_rate *float64 + addfee_rate *float64 + recharge_code *string + out_trade_no *string + payment_type *string + payment_trade_no *string + pay_url *string + qr_code *string + qr_code_img *string + order_type *string + plan_id *int64 + addplan_id *int64 + subscription_group_id *int64 + addsubscription_group_id *int64 + subscription_days *int + addsubscription_days *int + provider_instance_id *string + status *string + refund_amount *float64 + addrefund_amount *float64 + refund_reason *string + refund_at *time.Time + force_refund *bool + refund_requested_at *time.Time + refund_request_reason *string + refund_requested_by *string + expires_at *time.Time + paid_at *time.Time + completed_at *time.Time + failed_at *time.Time + failed_reason *string + client_ip *string + src_host *string + src_url *string + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + user *int64 + cleareduser bool + done bool + oldValue func(context.Context) (*PaymentOrder, error) + predicates []predicate.PaymentOrder +} + +var _ ent.Mutation = (*PaymentOrderMutation)(nil) + +// paymentorderOption allows management of the mutation configuration using functional options. +type paymentorderOption func(*PaymentOrderMutation) + +// newPaymentOrderMutation creates new mutation for the PaymentOrder entity. +func newPaymentOrderMutation(c config, op Op, opts ...paymentorderOption) *PaymentOrderMutation { + m := &PaymentOrderMutation{ + config: c, + op: op, + typ: TypePaymentOrder, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m } -// 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] +// withPaymentOrderID sets the ID field of the mutation. +func withPaymentOrderID(id int64) paymentorderOption { + return func(m *PaymentOrderMutation) { + var ( + err error + once sync.Once + value *PaymentOrder + ) + m.oldValue = func(ctx context.Context) (*PaymentOrder, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().PaymentOrder.Get(ctx, id) + } + }) + return value, err + } + m.id = &id } - m.Where(p...) } -// Op returns the operation name. -func (m *PromoCodeMutation) Op() Op { - return m.op +// withPaymentOrder sets the old PaymentOrder of the mutation. +func withPaymentOrder(node *PaymentOrder) paymentorderOption { + return func(m *PaymentOrderMutation) { + m.oldValue = func(context.Context) (*PaymentOrder, error) { + return node, nil + } + m.id = &node.ID + } } -// SetOp allows setting the mutation operation. -func (m *PromoCodeMutation) SetOp(op Op) { - m.op = op +// 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 PaymentOrderMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client } -// Type returns the node type of this mutation (PromoCode). -func (m *PromoCodeMutation) Type() string { - return m.typ +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m PaymentOrderMutation) 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 } -// 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) +// 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 *PaymentOrderMutation) ID() (id int64, exists bool) { + if m.id == nil { + return } - if m.bonus_amount != nil { - fields = append(fields, promocode.FieldBonusAmount) + 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 *PaymentOrderMutation) 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().PaymentOrder.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } - if m.max_uses != nil { - fields = append(fields, promocode.FieldMaxUses) +} + +// SetUserID sets the "user_id" field. +func (m *PaymentOrderMutation) SetUserID(i int64) { + m.user = &i +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *PaymentOrderMutation) UserID() (r int64, exists bool) { + v := m.user + if v == nil { + return } - if m.used_count != nil { - fields = append(fields, promocode.FieldUsedCount) + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) 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.status != nil { - fields = append(fields, promocode.FieldStatus) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") } - if m.expires_at != nil { - fields = append(fields, promocode.FieldExpiresAt) + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) } - if m.notes != nil { - fields = append(fields, promocode.FieldNotes) + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *PaymentOrderMutation) ResetUserID() { + m.user = nil +} + +// SetUserEmail sets the "user_email" field. +func (m *PaymentOrderMutation) SetUserEmail(s string) { + m.user_email = &s +} + +// UserEmail returns the value of the "user_email" field in the mutation. +func (m *PaymentOrderMutation) UserEmail() (r string, exists bool) { + v := m.user_email + if v == nil { + return } - if m.created_at != nil { - fields = append(fields, promocode.FieldCreatedAt) + return *v, true +} + +// OldUserEmail returns the old "user_email" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldUserEmail(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserEmail is only allowed on UpdateOne operations") } - if m.updated_at != nil { - fields = append(fields, promocode.FieldUpdatedAt) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserEmail requires an ID field in the mutation") } - return fields + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserEmail: %w", err) + } + return oldValue.UserEmail, nil } -// 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() +// ResetUserEmail resets all changes to the "user_email" field. +func (m *PaymentOrderMutation) ResetUserEmail() { + m.user_email = nil +} + +// SetUserName sets the "user_name" field. +func (m *PaymentOrderMutation) SetUserName(s string) { + m.user_name = &s +} + +// UserName returns the value of the "user_name" field in the mutation. +func (m *PaymentOrderMutation) UserName() (r string, exists bool) { + v := m.user_name + if v == nil { + return } - return nil, false + return *v, true } -// 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) +// OldUserName returns the old "user_name" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldUserName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserName is only allowed on UpdateOne operations") } - return nil, fmt.Errorf("unknown PromoCode field %s", name) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserName: %w", err) + } + return oldValue.UserName, nil } -// 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) - } +// ResetUserName resets all changes to the "user_name" field. +func (m *PaymentOrderMutation) ResetUserName() { + m.user_name = nil +} + +// SetUserNotes sets the "user_notes" field. +func (m *PaymentOrderMutation) SetUserNotes(s string) { + m.user_notes = &s +} + +// UserNotes returns the value of the "user_notes" field in the mutation. +func (m *PaymentOrderMutation) UserNotes() (r string, exists bool) { + v := m.user_notes + if v == nil { + return + } + return *v, true +} + +// OldUserNotes returns the old "user_notes" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldUserNotes(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserNotes is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserNotes requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserNotes: %w", err) + } + return oldValue.UserNotes, nil +} + +// ClearUserNotes clears the value of the "user_notes" field. +func (m *PaymentOrderMutation) ClearUserNotes() { + m.user_notes = nil + m.clearedFields[paymentorder.FieldUserNotes] = struct{}{} +} + +// UserNotesCleared returns if the "user_notes" field was cleared in this mutation. +func (m *PaymentOrderMutation) UserNotesCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldUserNotes] + return ok +} + +// ResetUserNotes resets all changes to the "user_notes" field. +func (m *PaymentOrderMutation) ResetUserNotes() { + m.user_notes = nil + delete(m.clearedFields, paymentorder.FieldUserNotes) +} + +// SetAmount sets the "amount" field. +func (m *PaymentOrderMutation) SetAmount(f float64) { + m.amount = &f + m.addamount = nil +} + +// Amount returns the value of the "amount" field in the mutation. +func (m *PaymentOrderMutation) Amount() (r float64, exists bool) { + v := m.amount + if v == nil { + return + } + return *v, true +} + +// OldAmount returns the old "amount" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldAmount(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAmount is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAmount requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAmount: %w", err) + } + return oldValue.Amount, nil +} + +// AddAmount adds f to the "amount" field. +func (m *PaymentOrderMutation) AddAmount(f float64) { + if m.addamount != nil { + *m.addamount += f + } else { + m.addamount = &f + } +} + +// AddedAmount returns the value that was added to the "amount" field in this mutation. +func (m *PaymentOrderMutation) AddedAmount() (r float64, exists bool) { + v := m.addamount + if v == nil { + return + } + return *v, true +} + +// ResetAmount resets all changes to the "amount" field. +func (m *PaymentOrderMutation) ResetAmount() { + m.amount = nil + m.addamount = nil +} + +// SetPayAmount sets the "pay_amount" field. +func (m *PaymentOrderMutation) SetPayAmount(f float64) { + m.pay_amount = &f + m.addpay_amount = nil +} + +// PayAmount returns the value of the "pay_amount" field in the mutation. +func (m *PaymentOrderMutation) PayAmount() (r float64, exists bool) { + v := m.pay_amount + if v == nil { + return + } + return *v, true +} + +// OldPayAmount returns the old "pay_amount" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldPayAmount(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPayAmount is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPayAmount requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPayAmount: %w", err) + } + return oldValue.PayAmount, nil +} + +// AddPayAmount adds f to the "pay_amount" field. +func (m *PaymentOrderMutation) AddPayAmount(f float64) { + if m.addpay_amount != nil { + *m.addpay_amount += f + } else { + m.addpay_amount = &f + } +} + +// AddedPayAmount returns the value that was added to the "pay_amount" field in this mutation. +func (m *PaymentOrderMutation) AddedPayAmount() (r float64, exists bool) { + v := m.addpay_amount + if v == nil { + return + } + return *v, true +} + +// ResetPayAmount resets all changes to the "pay_amount" field. +func (m *PaymentOrderMutation) ResetPayAmount() { + m.pay_amount = nil + m.addpay_amount = nil +} + +// SetFeeRate sets the "fee_rate" field. +func (m *PaymentOrderMutation) SetFeeRate(f float64) { + m.fee_rate = &f + m.addfee_rate = nil +} + +// FeeRate returns the value of the "fee_rate" field in the mutation. +func (m *PaymentOrderMutation) FeeRate() (r float64, exists bool) { + v := m.fee_rate + if v == nil { + return + } + return *v, true +} + +// OldFeeRate returns the old "fee_rate" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldFeeRate(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFeeRate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFeeRate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFeeRate: %w", err) + } + return oldValue.FeeRate, nil +} + +// AddFeeRate adds f to the "fee_rate" field. +func (m *PaymentOrderMutation) AddFeeRate(f float64) { + if m.addfee_rate != nil { + *m.addfee_rate += f + } else { + m.addfee_rate = &f + } +} + +// AddedFeeRate returns the value that was added to the "fee_rate" field in this mutation. +func (m *PaymentOrderMutation) AddedFeeRate() (r float64, exists bool) { + v := m.addfee_rate + if v == nil { + return + } + return *v, true +} + +// ResetFeeRate resets all changes to the "fee_rate" field. +func (m *PaymentOrderMutation) ResetFeeRate() { + m.fee_rate = nil + m.addfee_rate = nil +} + +// SetRechargeCode sets the "recharge_code" field. +func (m *PaymentOrderMutation) SetRechargeCode(s string) { + m.recharge_code = &s +} + +// RechargeCode returns the value of the "recharge_code" field in the mutation. +func (m *PaymentOrderMutation) RechargeCode() (r string, exists bool) { + v := m.recharge_code + if v == nil { + return + } + return *v, true +} + +// OldRechargeCode returns the old "recharge_code" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRechargeCode(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRechargeCode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRechargeCode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRechargeCode: %w", err) + } + return oldValue.RechargeCode, nil +} + +// ResetRechargeCode resets all changes to the "recharge_code" field. +func (m *PaymentOrderMutation) ResetRechargeCode() { + m.recharge_code = nil +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (m *PaymentOrderMutation) SetOutTradeNo(s string) { + m.out_trade_no = &s +} + +// OutTradeNo returns the value of the "out_trade_no" field in the mutation. +func (m *PaymentOrderMutation) OutTradeNo() (r string, exists bool) { + v := m.out_trade_no + if v == nil { + return + } + return *v, true +} + +// OldOutTradeNo returns the old "out_trade_no" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldOutTradeNo(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOutTradeNo is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOutTradeNo requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOutTradeNo: %w", err) + } + return oldValue.OutTradeNo, nil +} + +// ResetOutTradeNo resets all changes to the "out_trade_no" field. +func (m *PaymentOrderMutation) ResetOutTradeNo() { + m.out_trade_no = nil +} + +// SetPaymentType sets the "payment_type" field. +func (m *PaymentOrderMutation) SetPaymentType(s string) { + m.payment_type = &s +} + +// PaymentType returns the value of the "payment_type" field in the mutation. +func (m *PaymentOrderMutation) PaymentType() (r string, exists bool) { + v := m.payment_type + if v == nil { + return + } + return *v, true +} + +// OldPaymentType returns the old "payment_type" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldPaymentType(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPaymentType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPaymentType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPaymentType: %w", err) + } + return oldValue.PaymentType, nil +} + +// ResetPaymentType resets all changes to the "payment_type" field. +func (m *PaymentOrderMutation) ResetPaymentType() { + m.payment_type = nil +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (m *PaymentOrderMutation) SetPaymentTradeNo(s string) { + m.payment_trade_no = &s +} + +// PaymentTradeNo returns the value of the "payment_trade_no" field in the mutation. +func (m *PaymentOrderMutation) PaymentTradeNo() (r string, exists bool) { + v := m.payment_trade_no + if v == nil { + return + } + return *v, true +} + +// OldPaymentTradeNo returns the old "payment_trade_no" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldPaymentTradeNo(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPaymentTradeNo is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPaymentTradeNo requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPaymentTradeNo: %w", err) + } + return oldValue.PaymentTradeNo, nil +} + +// ResetPaymentTradeNo resets all changes to the "payment_trade_no" field. +func (m *PaymentOrderMutation) ResetPaymentTradeNo() { + m.payment_trade_no = nil +} + +// SetPayURL sets the "pay_url" field. +func (m *PaymentOrderMutation) SetPayURL(s string) { + m.pay_url = &s +} + +// PayURL returns the value of the "pay_url" field in the mutation. +func (m *PaymentOrderMutation) PayURL() (r string, exists bool) { + v := m.pay_url + if v == nil { + return + } + return *v, true +} + +// OldPayURL returns the old "pay_url" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldPayURL(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPayURL is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPayURL requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPayURL: %w", err) + } + return oldValue.PayURL, nil +} + +// ClearPayURL clears the value of the "pay_url" field. +func (m *PaymentOrderMutation) ClearPayURL() { + m.pay_url = nil + m.clearedFields[paymentorder.FieldPayURL] = struct{}{} +} + +// PayURLCleared returns if the "pay_url" field was cleared in this mutation. +func (m *PaymentOrderMutation) PayURLCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldPayURL] + return ok +} + +// ResetPayURL resets all changes to the "pay_url" field. +func (m *PaymentOrderMutation) ResetPayURL() { + m.pay_url = nil + delete(m.clearedFields, paymentorder.FieldPayURL) +} + +// SetQrCode sets the "qr_code" field. +func (m *PaymentOrderMutation) SetQrCode(s string) { + m.qr_code = &s +} + +// QrCode returns the value of the "qr_code" field in the mutation. +func (m *PaymentOrderMutation) QrCode() (r string, exists bool) { + v := m.qr_code + if v == nil { + return + } + return *v, true +} + +// OldQrCode returns the old "qr_code" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldQrCode(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldQrCode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldQrCode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldQrCode: %w", err) + } + return oldValue.QrCode, nil +} + +// ClearQrCode clears the value of the "qr_code" field. +func (m *PaymentOrderMutation) ClearQrCode() { + m.qr_code = nil + m.clearedFields[paymentorder.FieldQrCode] = struct{}{} +} + +// QrCodeCleared returns if the "qr_code" field was cleared in this mutation. +func (m *PaymentOrderMutation) QrCodeCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldQrCode] + return ok +} + +// ResetQrCode resets all changes to the "qr_code" field. +func (m *PaymentOrderMutation) ResetQrCode() { + m.qr_code = nil + delete(m.clearedFields, paymentorder.FieldQrCode) +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (m *PaymentOrderMutation) SetQrCodeImg(s string) { + m.qr_code_img = &s +} + +// QrCodeImg returns the value of the "qr_code_img" field in the mutation. +func (m *PaymentOrderMutation) QrCodeImg() (r string, exists bool) { + v := m.qr_code_img + if v == nil { + return + } + return *v, true +} + +// OldQrCodeImg returns the old "qr_code_img" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldQrCodeImg(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldQrCodeImg is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldQrCodeImg requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldQrCodeImg: %w", err) + } + return oldValue.QrCodeImg, nil +} + +// ClearQrCodeImg clears the value of the "qr_code_img" field. +func (m *PaymentOrderMutation) ClearQrCodeImg() { + m.qr_code_img = nil + m.clearedFields[paymentorder.FieldQrCodeImg] = struct{}{} +} + +// QrCodeImgCleared returns if the "qr_code_img" field was cleared in this mutation. +func (m *PaymentOrderMutation) QrCodeImgCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldQrCodeImg] + return ok +} + +// ResetQrCodeImg resets all changes to the "qr_code_img" field. +func (m *PaymentOrderMutation) ResetQrCodeImg() { + m.qr_code_img = nil + delete(m.clearedFields, paymentorder.FieldQrCodeImg) +} + +// SetOrderType sets the "order_type" field. +func (m *PaymentOrderMutation) SetOrderType(s string) { + m.order_type = &s +} + +// OrderType returns the value of the "order_type" field in the mutation. +func (m *PaymentOrderMutation) OrderType() (r string, exists bool) { + v := m.order_type + if v == nil { + return + } + return *v, true +} + +// OldOrderType returns the old "order_type" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldOrderType(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOrderType is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOrderType requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOrderType: %w", err) + } + return oldValue.OrderType, nil +} + +// ResetOrderType resets all changes to the "order_type" field. +func (m *PaymentOrderMutation) ResetOrderType() { + m.order_type = nil +} + +// SetPlanID sets the "plan_id" field. +func (m *PaymentOrderMutation) SetPlanID(i int64) { + m.plan_id = &i + m.addplan_id = nil +} + +// PlanID returns the value of the "plan_id" field in the mutation. +func (m *PaymentOrderMutation) PlanID() (r int64, exists bool) { + v := m.plan_id + if v == nil { + return + } + return *v, true +} + +// OldPlanID returns the old "plan_id" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldPlanID(ctx context.Context) (v *int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPlanID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPlanID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPlanID: %w", err) + } + return oldValue.PlanID, nil +} + +// AddPlanID adds i to the "plan_id" field. +func (m *PaymentOrderMutation) AddPlanID(i int64) { + if m.addplan_id != nil { + *m.addplan_id += i + } else { + m.addplan_id = &i + } +} + +// AddedPlanID returns the value that was added to the "plan_id" field in this mutation. +func (m *PaymentOrderMutation) AddedPlanID() (r int64, exists bool) { + v := m.addplan_id + if v == nil { + return + } + return *v, true +} + +// ClearPlanID clears the value of the "plan_id" field. +func (m *PaymentOrderMutation) ClearPlanID() { + m.plan_id = nil + m.addplan_id = nil + m.clearedFields[paymentorder.FieldPlanID] = struct{}{} +} + +// PlanIDCleared returns if the "plan_id" field was cleared in this mutation. +func (m *PaymentOrderMutation) PlanIDCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldPlanID] + return ok +} + +// ResetPlanID resets all changes to the "plan_id" field. +func (m *PaymentOrderMutation) ResetPlanID() { + m.plan_id = nil + m.addplan_id = nil + delete(m.clearedFields, paymentorder.FieldPlanID) +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (m *PaymentOrderMutation) SetSubscriptionGroupID(i int64) { + m.subscription_group_id = &i + m.addsubscription_group_id = nil +} + +// SubscriptionGroupID returns the value of the "subscription_group_id" field in the mutation. +func (m *PaymentOrderMutation) SubscriptionGroupID() (r int64, exists bool) { + v := m.subscription_group_id + if v == nil { + return + } + return *v, true +} + +// OldSubscriptionGroupID returns the old "subscription_group_id" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldSubscriptionGroupID(ctx context.Context) (v *int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSubscriptionGroupID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSubscriptionGroupID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSubscriptionGroupID: %w", err) + } + return oldValue.SubscriptionGroupID, nil +} + +// AddSubscriptionGroupID adds i to the "subscription_group_id" field. +func (m *PaymentOrderMutation) AddSubscriptionGroupID(i int64) { + if m.addsubscription_group_id != nil { + *m.addsubscription_group_id += i + } else { + m.addsubscription_group_id = &i + } +} + +// AddedSubscriptionGroupID returns the value that was added to the "subscription_group_id" field in this mutation. +func (m *PaymentOrderMutation) AddedSubscriptionGroupID() (r int64, exists bool) { + v := m.addsubscription_group_id + if v == nil { + return + } + return *v, true +} + +// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field. +func (m *PaymentOrderMutation) ClearSubscriptionGroupID() { + m.subscription_group_id = nil + m.addsubscription_group_id = nil + m.clearedFields[paymentorder.FieldSubscriptionGroupID] = struct{}{} +} + +// SubscriptionGroupIDCleared returns if the "subscription_group_id" field was cleared in this mutation. +func (m *PaymentOrderMutation) SubscriptionGroupIDCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldSubscriptionGroupID] + return ok +} + +// ResetSubscriptionGroupID resets all changes to the "subscription_group_id" field. +func (m *PaymentOrderMutation) ResetSubscriptionGroupID() { + m.subscription_group_id = nil + m.addsubscription_group_id = nil + delete(m.clearedFields, paymentorder.FieldSubscriptionGroupID) +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (m *PaymentOrderMutation) SetSubscriptionDays(i int) { + m.subscription_days = &i + m.addsubscription_days = nil +} + +// SubscriptionDays returns the value of the "subscription_days" field in the mutation. +func (m *PaymentOrderMutation) SubscriptionDays() (r int, exists bool) { + v := m.subscription_days + if v == nil { + return + } + return *v, true +} + +// OldSubscriptionDays returns the old "subscription_days" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldSubscriptionDays(ctx context.Context) (v *int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSubscriptionDays is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSubscriptionDays requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSubscriptionDays: %w", err) + } + return oldValue.SubscriptionDays, nil +} + +// AddSubscriptionDays adds i to the "subscription_days" field. +func (m *PaymentOrderMutation) AddSubscriptionDays(i int) { + if m.addsubscription_days != nil { + *m.addsubscription_days += i + } else { + m.addsubscription_days = &i + } +} + +// AddedSubscriptionDays returns the value that was added to the "subscription_days" field in this mutation. +func (m *PaymentOrderMutation) AddedSubscriptionDays() (r int, exists bool) { + v := m.addsubscription_days + if v == nil { + return + } + return *v, true +} + +// ClearSubscriptionDays clears the value of the "subscription_days" field. +func (m *PaymentOrderMutation) ClearSubscriptionDays() { + m.subscription_days = nil + m.addsubscription_days = nil + m.clearedFields[paymentorder.FieldSubscriptionDays] = struct{}{} +} + +// SubscriptionDaysCleared returns if the "subscription_days" field was cleared in this mutation. +func (m *PaymentOrderMutation) SubscriptionDaysCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldSubscriptionDays] + return ok +} + +// ResetSubscriptionDays resets all changes to the "subscription_days" field. +func (m *PaymentOrderMutation) ResetSubscriptionDays() { + m.subscription_days = nil + m.addsubscription_days = nil + delete(m.clearedFields, paymentorder.FieldSubscriptionDays) +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (m *PaymentOrderMutation) SetProviderInstanceID(s string) { + m.provider_instance_id = &s +} + +// ProviderInstanceID returns the value of the "provider_instance_id" field in the mutation. +func (m *PaymentOrderMutation) ProviderInstanceID() (r string, exists bool) { + v := m.provider_instance_id + if v == nil { + return + } + return *v, true +} + +// OldProviderInstanceID returns the old "provider_instance_id" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldProviderInstanceID(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProviderInstanceID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProviderInstanceID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProviderInstanceID: %w", err) + } + return oldValue.ProviderInstanceID, nil +} + +// ClearProviderInstanceID clears the value of the "provider_instance_id" field. +func (m *PaymentOrderMutation) ClearProviderInstanceID() { + m.provider_instance_id = nil + m.clearedFields[paymentorder.FieldProviderInstanceID] = struct{}{} +} + +// ProviderInstanceIDCleared returns if the "provider_instance_id" field was cleared in this mutation. +func (m *PaymentOrderMutation) ProviderInstanceIDCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldProviderInstanceID] + return ok +} + +// ResetProviderInstanceID resets all changes to the "provider_instance_id" field. +func (m *PaymentOrderMutation) ResetProviderInstanceID() { + m.provider_instance_id = nil + delete(m.clearedFields, paymentorder.FieldProviderInstanceID) +} + +// SetStatus sets the "status" field. +func (m *PaymentOrderMutation) SetStatus(s string) { + m.status = &s +} + +// Status returns the value of the "status" field in the mutation. +func (m *PaymentOrderMutation) 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 PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) 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 *PaymentOrderMutation) ResetStatus() { + m.status = nil +} + +// SetRefundAmount sets the "refund_amount" field. +func (m *PaymentOrderMutation) SetRefundAmount(f float64) { + m.refund_amount = &f + m.addrefund_amount = nil +} + +// RefundAmount returns the value of the "refund_amount" field in the mutation. +func (m *PaymentOrderMutation) RefundAmount() (r float64, exists bool) { + v := m.refund_amount + if v == nil { + return + } + return *v, true +} + +// OldRefundAmount returns the old "refund_amount" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRefundAmount(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundAmount is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundAmount requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundAmount: %w", err) + } + return oldValue.RefundAmount, nil +} + +// AddRefundAmount adds f to the "refund_amount" field. +func (m *PaymentOrderMutation) AddRefundAmount(f float64) { + if m.addrefund_amount != nil { + *m.addrefund_amount += f + } else { + m.addrefund_amount = &f + } +} + +// AddedRefundAmount returns the value that was added to the "refund_amount" field in this mutation. +func (m *PaymentOrderMutation) AddedRefundAmount() (r float64, exists bool) { + v := m.addrefund_amount + if v == nil { + return + } + return *v, true +} + +// ResetRefundAmount resets all changes to the "refund_amount" field. +func (m *PaymentOrderMutation) ResetRefundAmount() { + m.refund_amount = nil + m.addrefund_amount = nil +} + +// SetRefundReason sets the "refund_reason" field. +func (m *PaymentOrderMutation) SetRefundReason(s string) { + m.refund_reason = &s +} + +// RefundReason returns the value of the "refund_reason" field in the mutation. +func (m *PaymentOrderMutation) RefundReason() (r string, exists bool) { + v := m.refund_reason + if v == nil { + return + } + return *v, true +} + +// OldRefundReason returns the old "refund_reason" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRefundReason(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundReason is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundReason requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundReason: %w", err) + } + return oldValue.RefundReason, nil +} + +// ClearRefundReason clears the value of the "refund_reason" field. +func (m *PaymentOrderMutation) ClearRefundReason() { + m.refund_reason = nil + m.clearedFields[paymentorder.FieldRefundReason] = struct{}{} +} + +// RefundReasonCleared returns if the "refund_reason" field was cleared in this mutation. +func (m *PaymentOrderMutation) RefundReasonCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldRefundReason] + return ok +} + +// ResetRefundReason resets all changes to the "refund_reason" field. +func (m *PaymentOrderMutation) ResetRefundReason() { + m.refund_reason = nil + delete(m.clearedFields, paymentorder.FieldRefundReason) +} + +// SetRefundAt sets the "refund_at" field. +func (m *PaymentOrderMutation) SetRefundAt(t time.Time) { + m.refund_at = &t +} + +// RefundAt returns the value of the "refund_at" field in the mutation. +func (m *PaymentOrderMutation) RefundAt() (r time.Time, exists bool) { + v := m.refund_at + if v == nil { + return + } + return *v, true +} + +// OldRefundAt returns the old "refund_at" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRefundAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundAt: %w", err) + } + return oldValue.RefundAt, nil +} + +// ClearRefundAt clears the value of the "refund_at" field. +func (m *PaymentOrderMutation) ClearRefundAt() { + m.refund_at = nil + m.clearedFields[paymentorder.FieldRefundAt] = struct{}{} +} + +// RefundAtCleared returns if the "refund_at" field was cleared in this mutation. +func (m *PaymentOrderMutation) RefundAtCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldRefundAt] + return ok +} + +// ResetRefundAt resets all changes to the "refund_at" field. +func (m *PaymentOrderMutation) ResetRefundAt() { + m.refund_at = nil + delete(m.clearedFields, paymentorder.FieldRefundAt) +} + +// SetForceRefund sets the "force_refund" field. +func (m *PaymentOrderMutation) SetForceRefund(b bool) { + m.force_refund = &b +} + +// ForceRefund returns the value of the "force_refund" field in the mutation. +func (m *PaymentOrderMutation) ForceRefund() (r bool, exists bool) { + v := m.force_refund + if v == nil { + return + } + return *v, true +} + +// OldForceRefund returns the old "force_refund" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldForceRefund(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldForceRefund is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldForceRefund requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldForceRefund: %w", err) + } + return oldValue.ForceRefund, nil +} + +// ResetForceRefund resets all changes to the "force_refund" field. +func (m *PaymentOrderMutation) ResetForceRefund() { + m.force_refund = nil +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (m *PaymentOrderMutation) SetRefundRequestedAt(t time.Time) { + m.refund_requested_at = &t +} + +// RefundRequestedAt returns the value of the "refund_requested_at" field in the mutation. +func (m *PaymentOrderMutation) RefundRequestedAt() (r time.Time, exists bool) { + v := m.refund_requested_at + if v == nil { + return + } + return *v, true +} + +// OldRefundRequestedAt returns the old "refund_requested_at" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRefundRequestedAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundRequestedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundRequestedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundRequestedAt: %w", err) + } + return oldValue.RefundRequestedAt, nil +} + +// ClearRefundRequestedAt clears the value of the "refund_requested_at" field. +func (m *PaymentOrderMutation) ClearRefundRequestedAt() { + m.refund_requested_at = nil + m.clearedFields[paymentorder.FieldRefundRequestedAt] = struct{}{} +} + +// RefundRequestedAtCleared returns if the "refund_requested_at" field was cleared in this mutation. +func (m *PaymentOrderMutation) RefundRequestedAtCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldRefundRequestedAt] + return ok +} + +// ResetRefundRequestedAt resets all changes to the "refund_requested_at" field. +func (m *PaymentOrderMutation) ResetRefundRequestedAt() { + m.refund_requested_at = nil + delete(m.clearedFields, paymentorder.FieldRefundRequestedAt) +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (m *PaymentOrderMutation) SetRefundRequestReason(s string) { + m.refund_request_reason = &s +} + +// RefundRequestReason returns the value of the "refund_request_reason" field in the mutation. +func (m *PaymentOrderMutation) RefundRequestReason() (r string, exists bool) { + v := m.refund_request_reason + if v == nil { + return + } + return *v, true +} + +// OldRefundRequestReason returns the old "refund_request_reason" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRefundRequestReason(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundRequestReason is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundRequestReason requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundRequestReason: %w", err) + } + return oldValue.RefundRequestReason, nil +} + +// ClearRefundRequestReason clears the value of the "refund_request_reason" field. +func (m *PaymentOrderMutation) ClearRefundRequestReason() { + m.refund_request_reason = nil + m.clearedFields[paymentorder.FieldRefundRequestReason] = struct{}{} +} + +// RefundRequestReasonCleared returns if the "refund_request_reason" field was cleared in this mutation. +func (m *PaymentOrderMutation) RefundRequestReasonCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldRefundRequestReason] + return ok +} + +// ResetRefundRequestReason resets all changes to the "refund_request_reason" field. +func (m *PaymentOrderMutation) ResetRefundRequestReason() { + m.refund_request_reason = nil + delete(m.clearedFields, paymentorder.FieldRefundRequestReason) +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (m *PaymentOrderMutation) SetRefundRequestedBy(s string) { + m.refund_requested_by = &s +} + +// RefundRequestedBy returns the value of the "refund_requested_by" field in the mutation. +func (m *PaymentOrderMutation) RefundRequestedBy() (r string, exists bool) { + v := m.refund_requested_by + if v == nil { + return + } + return *v, true +} + +// OldRefundRequestedBy returns the old "refund_requested_by" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldRefundRequestedBy(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundRequestedBy is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundRequestedBy requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundRequestedBy: %w", err) + } + return oldValue.RefundRequestedBy, nil +} + +// ClearRefundRequestedBy clears the value of the "refund_requested_by" field. +func (m *PaymentOrderMutation) ClearRefundRequestedBy() { + m.refund_requested_by = nil + m.clearedFields[paymentorder.FieldRefundRequestedBy] = struct{}{} +} + +// RefundRequestedByCleared returns if the "refund_requested_by" field was cleared in this mutation. +func (m *PaymentOrderMutation) RefundRequestedByCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldRefundRequestedBy] + return ok +} + +// ResetRefundRequestedBy resets all changes to the "refund_requested_by" field. +func (m *PaymentOrderMutation) ResetRefundRequestedBy() { + m.refund_requested_by = nil + delete(m.clearedFields, paymentorder.FieldRefundRequestedBy) +} + +// SetExpiresAt sets the "expires_at" field. +func (m *PaymentOrderMutation) SetExpiresAt(t time.Time) { + m.expires_at = &t +} + +// ExpiresAt returns the value of the "expires_at" field in the mutation. +func (m *PaymentOrderMutation) 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 PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) 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 +} + +// ResetExpiresAt resets all changes to the "expires_at" field. +func (m *PaymentOrderMutation) ResetExpiresAt() { + m.expires_at = nil +} + +// SetPaidAt sets the "paid_at" field. +func (m *PaymentOrderMutation) SetPaidAt(t time.Time) { + m.paid_at = &t +} + +// PaidAt returns the value of the "paid_at" field in the mutation. +func (m *PaymentOrderMutation) PaidAt() (r time.Time, exists bool) { + v := m.paid_at + if v == nil { + return + } + return *v, true +} + +// OldPaidAt returns the old "paid_at" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldPaidAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPaidAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPaidAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPaidAt: %w", err) + } + return oldValue.PaidAt, nil +} + +// ClearPaidAt clears the value of the "paid_at" field. +func (m *PaymentOrderMutation) ClearPaidAt() { + m.paid_at = nil + m.clearedFields[paymentorder.FieldPaidAt] = struct{}{} +} + +// PaidAtCleared returns if the "paid_at" field was cleared in this mutation. +func (m *PaymentOrderMutation) PaidAtCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldPaidAt] + return ok +} + +// ResetPaidAt resets all changes to the "paid_at" field. +func (m *PaymentOrderMutation) ResetPaidAt() { + m.paid_at = nil + delete(m.clearedFields, paymentorder.FieldPaidAt) +} + +// SetCompletedAt sets the "completed_at" field. +func (m *PaymentOrderMutation) SetCompletedAt(t time.Time) { + m.completed_at = &t +} + +// CompletedAt returns the value of the "completed_at" field in the mutation. +func (m *PaymentOrderMutation) CompletedAt() (r time.Time, exists bool) { + v := m.completed_at + if v == nil { + return + } + return *v, true +} + +// OldCompletedAt returns the old "completed_at" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldCompletedAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCompletedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCompletedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCompletedAt: %w", err) + } + return oldValue.CompletedAt, nil +} + +// ClearCompletedAt clears the value of the "completed_at" field. +func (m *PaymentOrderMutation) ClearCompletedAt() { + m.completed_at = nil + m.clearedFields[paymentorder.FieldCompletedAt] = struct{}{} +} + +// CompletedAtCleared returns if the "completed_at" field was cleared in this mutation. +func (m *PaymentOrderMutation) CompletedAtCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldCompletedAt] + return ok +} + +// ResetCompletedAt resets all changes to the "completed_at" field. +func (m *PaymentOrderMutation) ResetCompletedAt() { + m.completed_at = nil + delete(m.clearedFields, paymentorder.FieldCompletedAt) +} + +// SetFailedAt sets the "failed_at" field. +func (m *PaymentOrderMutation) SetFailedAt(t time.Time) { + m.failed_at = &t +} + +// FailedAt returns the value of the "failed_at" field in the mutation. +func (m *PaymentOrderMutation) FailedAt() (r time.Time, exists bool) { + v := m.failed_at + if v == nil { + return + } + return *v, true +} + +// OldFailedAt returns the old "failed_at" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldFailedAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFailedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFailedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFailedAt: %w", err) + } + return oldValue.FailedAt, nil +} + +// ClearFailedAt clears the value of the "failed_at" field. +func (m *PaymentOrderMutation) ClearFailedAt() { + m.failed_at = nil + m.clearedFields[paymentorder.FieldFailedAt] = struct{}{} +} + +// FailedAtCleared returns if the "failed_at" field was cleared in this mutation. +func (m *PaymentOrderMutation) FailedAtCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldFailedAt] + return ok +} + +// ResetFailedAt resets all changes to the "failed_at" field. +func (m *PaymentOrderMutation) ResetFailedAt() { + m.failed_at = nil + delete(m.clearedFields, paymentorder.FieldFailedAt) +} + +// SetFailedReason sets the "failed_reason" field. +func (m *PaymentOrderMutation) SetFailedReason(s string) { + m.failed_reason = &s +} + +// FailedReason returns the value of the "failed_reason" field in the mutation. +func (m *PaymentOrderMutation) FailedReason() (r string, exists bool) { + v := m.failed_reason + if v == nil { + return + } + return *v, true +} + +// OldFailedReason returns the old "failed_reason" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldFailedReason(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFailedReason is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFailedReason requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFailedReason: %w", err) + } + return oldValue.FailedReason, nil +} + +// ClearFailedReason clears the value of the "failed_reason" field. +func (m *PaymentOrderMutation) ClearFailedReason() { + m.failed_reason = nil + m.clearedFields[paymentorder.FieldFailedReason] = struct{}{} +} + +// FailedReasonCleared returns if the "failed_reason" field was cleared in this mutation. +func (m *PaymentOrderMutation) FailedReasonCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldFailedReason] + return ok +} + +// ResetFailedReason resets all changes to the "failed_reason" field. +func (m *PaymentOrderMutation) ResetFailedReason() { + m.failed_reason = nil + delete(m.clearedFields, paymentorder.FieldFailedReason) +} + +// SetClientIP sets the "client_ip" field. +func (m *PaymentOrderMutation) SetClientIP(s string) { + m.client_ip = &s +} + +// ClientIP returns the value of the "client_ip" field in the mutation. +func (m *PaymentOrderMutation) ClientIP() (r string, exists bool) { + v := m.client_ip + if v == nil { + return + } + return *v, true +} + +// OldClientIP returns the old "client_ip" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldClientIP(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldClientIP is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldClientIP requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldClientIP: %w", err) + } + return oldValue.ClientIP, nil +} + +// ResetClientIP resets all changes to the "client_ip" field. +func (m *PaymentOrderMutation) ResetClientIP() { + m.client_ip = nil +} + +// SetSrcHost sets the "src_host" field. +func (m *PaymentOrderMutation) SetSrcHost(s string) { + m.src_host = &s +} + +// SrcHost returns the value of the "src_host" field in the mutation. +func (m *PaymentOrderMutation) SrcHost() (r string, exists bool) { + v := m.src_host + if v == nil { + return + } + return *v, true +} + +// OldSrcHost returns the old "src_host" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldSrcHost(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSrcHost is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSrcHost requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSrcHost: %w", err) + } + return oldValue.SrcHost, nil +} + +// ResetSrcHost resets all changes to the "src_host" field. +func (m *PaymentOrderMutation) ResetSrcHost() { + m.src_host = nil +} + +// SetSrcURL sets the "src_url" field. +func (m *PaymentOrderMutation) SetSrcURL(s string) { + m.src_url = &s +} + +// SrcURL returns the value of the "src_url" field in the mutation. +func (m *PaymentOrderMutation) SrcURL() (r string, exists bool) { + v := m.src_url + if v == nil { + return + } + return *v, true +} + +// OldSrcURL returns the old "src_url" field's value of the PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) OldSrcURL(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSrcURL is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSrcURL requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSrcURL: %w", err) + } + return oldValue.SrcURL, nil +} + +// ClearSrcURL clears the value of the "src_url" field. +func (m *PaymentOrderMutation) ClearSrcURL() { + m.src_url = nil + m.clearedFields[paymentorder.FieldSrcURL] = struct{}{} +} + +// SrcURLCleared returns if the "src_url" field was cleared in this mutation. +func (m *PaymentOrderMutation) SrcURLCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldSrcURL] + return ok +} + +// ResetSrcURL resets all changes to the "src_url" field. +func (m *PaymentOrderMutation) ResetSrcURL() { + m.src_url = nil + delete(m.clearedFields, paymentorder.FieldSrcURL) +} + +// SetCreatedAt sets the "created_at" field. +func (m *PaymentOrderMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *PaymentOrderMutation) 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 PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) 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 *PaymentOrderMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *PaymentOrderMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *PaymentOrderMutation) 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 PaymentOrder entity. +// If the PaymentOrder 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 *PaymentOrderMutation) 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 *PaymentOrderMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// ClearUser clears the "user" edge to the User entity. +func (m *PaymentOrderMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[paymentorder.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *PaymentOrderMutation) 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 *PaymentOrderMutation) UserIDs() (ids []int64) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *PaymentOrderMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the PaymentOrderMutation builder. +func (m *PaymentOrderMutation) Where(ps ...predicate.PaymentOrder) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the PaymentOrderMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *PaymentOrderMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.PaymentOrder, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *PaymentOrderMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *PaymentOrderMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (PaymentOrder). +func (m *PaymentOrderMutation) 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 *PaymentOrderMutation) Fields() []string { + fields := make([]string, 0, 37) + if m.user != nil { + fields = append(fields, paymentorder.FieldUserID) + } + if m.user_email != nil { + fields = append(fields, paymentorder.FieldUserEmail) + } + if m.user_name != nil { + fields = append(fields, paymentorder.FieldUserName) + } + if m.user_notes != nil { + fields = append(fields, paymentorder.FieldUserNotes) + } + if m.amount != nil { + fields = append(fields, paymentorder.FieldAmount) + } + if m.pay_amount != nil { + fields = append(fields, paymentorder.FieldPayAmount) + } + if m.fee_rate != nil { + fields = append(fields, paymentorder.FieldFeeRate) + } + if m.recharge_code != nil { + fields = append(fields, paymentorder.FieldRechargeCode) + } + if m.out_trade_no != nil { + fields = append(fields, paymentorder.FieldOutTradeNo) + } + if m.payment_type != nil { + fields = append(fields, paymentorder.FieldPaymentType) + } + if m.payment_trade_no != nil { + fields = append(fields, paymentorder.FieldPaymentTradeNo) + } + if m.pay_url != nil { + fields = append(fields, paymentorder.FieldPayURL) + } + if m.qr_code != nil { + fields = append(fields, paymentorder.FieldQrCode) + } + if m.qr_code_img != nil { + fields = append(fields, paymentorder.FieldQrCodeImg) + } + if m.order_type != nil { + fields = append(fields, paymentorder.FieldOrderType) + } + if m.plan_id != nil { + fields = append(fields, paymentorder.FieldPlanID) + } + if m.subscription_group_id != nil { + fields = append(fields, paymentorder.FieldSubscriptionGroupID) + } + if m.subscription_days != nil { + fields = append(fields, paymentorder.FieldSubscriptionDays) + } + if m.provider_instance_id != nil { + fields = append(fields, paymentorder.FieldProviderInstanceID) + } + if m.status != nil { + fields = append(fields, paymentorder.FieldStatus) + } + if m.refund_amount != nil { + fields = append(fields, paymentorder.FieldRefundAmount) + } + if m.refund_reason != nil { + fields = append(fields, paymentorder.FieldRefundReason) + } + if m.refund_at != nil { + fields = append(fields, paymentorder.FieldRefundAt) + } + if m.force_refund != nil { + fields = append(fields, paymentorder.FieldForceRefund) + } + if m.refund_requested_at != nil { + fields = append(fields, paymentorder.FieldRefundRequestedAt) + } + if m.refund_request_reason != nil { + fields = append(fields, paymentorder.FieldRefundRequestReason) + } + if m.refund_requested_by != nil { + fields = append(fields, paymentorder.FieldRefundRequestedBy) + } + if m.expires_at != nil { + fields = append(fields, paymentorder.FieldExpiresAt) + } + if m.paid_at != nil { + fields = append(fields, paymentorder.FieldPaidAt) + } + if m.completed_at != nil { + fields = append(fields, paymentorder.FieldCompletedAt) + } + if m.failed_at != nil { + fields = append(fields, paymentorder.FieldFailedAt) + } + if m.failed_reason != nil { + fields = append(fields, paymentorder.FieldFailedReason) + } + if m.client_ip != nil { + fields = append(fields, paymentorder.FieldClientIP) + } + if m.src_host != nil { + fields = append(fields, paymentorder.FieldSrcHost) + } + if m.src_url != nil { + fields = append(fields, paymentorder.FieldSrcURL) + } + if m.created_at != nil { + fields = append(fields, paymentorder.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, paymentorder.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 *PaymentOrderMutation) Field(name string) (ent.Value, bool) { + switch name { + case paymentorder.FieldUserID: + return m.UserID() + case paymentorder.FieldUserEmail: + return m.UserEmail() + case paymentorder.FieldUserName: + return m.UserName() + case paymentorder.FieldUserNotes: + return m.UserNotes() + case paymentorder.FieldAmount: + return m.Amount() + case paymentorder.FieldPayAmount: + return m.PayAmount() + case paymentorder.FieldFeeRate: + return m.FeeRate() + case paymentorder.FieldRechargeCode: + return m.RechargeCode() + case paymentorder.FieldOutTradeNo: + return m.OutTradeNo() + case paymentorder.FieldPaymentType: + return m.PaymentType() + case paymentorder.FieldPaymentTradeNo: + return m.PaymentTradeNo() + case paymentorder.FieldPayURL: + return m.PayURL() + case paymentorder.FieldQrCode: + return m.QrCode() + case paymentorder.FieldQrCodeImg: + return m.QrCodeImg() + case paymentorder.FieldOrderType: + return m.OrderType() + case paymentorder.FieldPlanID: + return m.PlanID() + case paymentorder.FieldSubscriptionGroupID: + return m.SubscriptionGroupID() + case paymentorder.FieldSubscriptionDays: + return m.SubscriptionDays() + case paymentorder.FieldProviderInstanceID: + return m.ProviderInstanceID() + case paymentorder.FieldStatus: + return m.Status() + case paymentorder.FieldRefundAmount: + return m.RefundAmount() + case paymentorder.FieldRefundReason: + return m.RefundReason() + case paymentorder.FieldRefundAt: + return m.RefundAt() + case paymentorder.FieldForceRefund: + return m.ForceRefund() + case paymentorder.FieldRefundRequestedAt: + return m.RefundRequestedAt() + case paymentorder.FieldRefundRequestReason: + return m.RefundRequestReason() + case paymentorder.FieldRefundRequestedBy: + return m.RefundRequestedBy() + case paymentorder.FieldExpiresAt: + return m.ExpiresAt() + case paymentorder.FieldPaidAt: + return m.PaidAt() + case paymentorder.FieldCompletedAt: + return m.CompletedAt() + case paymentorder.FieldFailedAt: + return m.FailedAt() + case paymentorder.FieldFailedReason: + return m.FailedReason() + case paymentorder.FieldClientIP: + return m.ClientIP() + case paymentorder.FieldSrcHost: + return m.SrcHost() + case paymentorder.FieldSrcURL: + return m.SrcURL() + case paymentorder.FieldCreatedAt: + return m.CreatedAt() + case paymentorder.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 *PaymentOrderMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case paymentorder.FieldUserID: + return m.OldUserID(ctx) + case paymentorder.FieldUserEmail: + return m.OldUserEmail(ctx) + case paymentorder.FieldUserName: + return m.OldUserName(ctx) + case paymentorder.FieldUserNotes: + return m.OldUserNotes(ctx) + case paymentorder.FieldAmount: + return m.OldAmount(ctx) + case paymentorder.FieldPayAmount: + return m.OldPayAmount(ctx) + case paymentorder.FieldFeeRate: + return m.OldFeeRate(ctx) + case paymentorder.FieldRechargeCode: + return m.OldRechargeCode(ctx) + case paymentorder.FieldOutTradeNo: + return m.OldOutTradeNo(ctx) + case paymentorder.FieldPaymentType: + return m.OldPaymentType(ctx) + case paymentorder.FieldPaymentTradeNo: + return m.OldPaymentTradeNo(ctx) + case paymentorder.FieldPayURL: + return m.OldPayURL(ctx) + case paymentorder.FieldQrCode: + return m.OldQrCode(ctx) + case paymentorder.FieldQrCodeImg: + return m.OldQrCodeImg(ctx) + case paymentorder.FieldOrderType: + return m.OldOrderType(ctx) + case paymentorder.FieldPlanID: + return m.OldPlanID(ctx) + case paymentorder.FieldSubscriptionGroupID: + return m.OldSubscriptionGroupID(ctx) + case paymentorder.FieldSubscriptionDays: + return m.OldSubscriptionDays(ctx) + case paymentorder.FieldProviderInstanceID: + return m.OldProviderInstanceID(ctx) + case paymentorder.FieldStatus: + return m.OldStatus(ctx) + case paymentorder.FieldRefundAmount: + return m.OldRefundAmount(ctx) + case paymentorder.FieldRefundReason: + return m.OldRefundReason(ctx) + case paymentorder.FieldRefundAt: + return m.OldRefundAt(ctx) + case paymentorder.FieldForceRefund: + return m.OldForceRefund(ctx) + case paymentorder.FieldRefundRequestedAt: + return m.OldRefundRequestedAt(ctx) + case paymentorder.FieldRefundRequestReason: + return m.OldRefundRequestReason(ctx) + case paymentorder.FieldRefundRequestedBy: + return m.OldRefundRequestedBy(ctx) + case paymentorder.FieldExpiresAt: + return m.OldExpiresAt(ctx) + case paymentorder.FieldPaidAt: + return m.OldPaidAt(ctx) + case paymentorder.FieldCompletedAt: + return m.OldCompletedAt(ctx) + case paymentorder.FieldFailedAt: + return m.OldFailedAt(ctx) + case paymentorder.FieldFailedReason: + return m.OldFailedReason(ctx) + case paymentorder.FieldClientIP: + return m.OldClientIP(ctx) + case paymentorder.FieldSrcHost: + return m.OldSrcHost(ctx) + case paymentorder.FieldSrcURL: + return m.OldSrcURL(ctx) + case paymentorder.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case paymentorder.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown PaymentOrder 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 *PaymentOrderMutation) SetField(name string, value ent.Value) error { + switch name { + case paymentorder.FieldUserID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case paymentorder.FieldUserEmail: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserEmail(v) + return nil + case paymentorder.FieldUserName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserName(v) + return nil + case paymentorder.FieldUserNotes: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserNotes(v) + return nil + case paymentorder.FieldAmount: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAmount(v) + return nil + case paymentorder.FieldPayAmount: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPayAmount(v) + return nil + case paymentorder.FieldFeeRate: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFeeRate(v) + return nil + case paymentorder.FieldRechargeCode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRechargeCode(v) + return nil + case paymentorder.FieldOutTradeNo: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOutTradeNo(v) + return nil + case paymentorder.FieldPaymentType: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPaymentType(v) + return nil + case paymentorder.FieldPaymentTradeNo: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPaymentTradeNo(v) + return nil + case paymentorder.FieldPayURL: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPayURL(v) + return nil + case paymentorder.FieldQrCode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetQrCode(v) + return nil + case paymentorder.FieldQrCodeImg: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetQrCodeImg(v) + return nil + case paymentorder.FieldOrderType: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOrderType(v) + return nil + case paymentorder.FieldPlanID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPlanID(v) + return nil + case paymentorder.FieldSubscriptionGroupID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSubscriptionGroupID(v) + return nil + case paymentorder.FieldSubscriptionDays: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSubscriptionDays(v) + return nil + case paymentorder.FieldProviderInstanceID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProviderInstanceID(v) + return nil + case paymentorder.FieldStatus: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStatus(v) + return nil + case paymentorder.FieldRefundAmount: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundAmount(v) + return nil + case paymentorder.FieldRefundReason: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundReason(v) + return nil + case paymentorder.FieldRefundAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundAt(v) + return nil + case paymentorder.FieldForceRefund: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetForceRefund(v) + return nil + case paymentorder.FieldRefundRequestedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundRequestedAt(v) + return nil + case paymentorder.FieldRefundRequestReason: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundRequestReason(v) + return nil + case paymentorder.FieldRefundRequestedBy: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundRequestedBy(v) + return nil + case paymentorder.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 paymentorder.FieldPaidAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPaidAt(v) + return nil + case paymentorder.FieldCompletedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCompletedAt(v) + return nil + case paymentorder.FieldFailedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFailedAt(v) + return nil + case paymentorder.FieldFailedReason: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFailedReason(v) + return nil + case paymentorder.FieldClientIP: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetClientIP(v) + return nil + case paymentorder.FieldSrcHost: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSrcHost(v) + return nil + case paymentorder.FieldSrcURL: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSrcURL(v) + return nil + case paymentorder.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 paymentorder.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 PaymentOrder field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *PaymentOrderMutation) AddedFields() []string { + var fields []string + if m.addamount != nil { + fields = append(fields, paymentorder.FieldAmount) + } + if m.addpay_amount != nil { + fields = append(fields, paymentorder.FieldPayAmount) + } + if m.addfee_rate != nil { + fields = append(fields, paymentorder.FieldFeeRate) + } + if m.addplan_id != nil { + fields = append(fields, paymentorder.FieldPlanID) + } + if m.addsubscription_group_id != nil { + fields = append(fields, paymentorder.FieldSubscriptionGroupID) + } + if m.addsubscription_days != nil { + fields = append(fields, paymentorder.FieldSubscriptionDays) + } + if m.addrefund_amount != nil { + fields = append(fields, paymentorder.FieldRefundAmount) + } + 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 *PaymentOrderMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case paymentorder.FieldAmount: + return m.AddedAmount() + case paymentorder.FieldPayAmount: + return m.AddedPayAmount() + case paymentorder.FieldFeeRate: + return m.AddedFeeRate() + case paymentorder.FieldPlanID: + return m.AddedPlanID() + case paymentorder.FieldSubscriptionGroupID: + return m.AddedSubscriptionGroupID() + case paymentorder.FieldSubscriptionDays: + return m.AddedSubscriptionDays() + case paymentorder.FieldRefundAmount: + return m.AddedRefundAmount() + } + 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 *PaymentOrderMutation) AddField(name string, value ent.Value) error { + switch name { + case paymentorder.FieldAmount: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddAmount(v) + return nil + case paymentorder.FieldPayAmount: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPayAmount(v) + return nil + case paymentorder.FieldFeeRate: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddFeeRate(v) + return nil + case paymentorder.FieldPlanID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPlanID(v) + return nil + case paymentorder.FieldSubscriptionGroupID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSubscriptionGroupID(v) + return nil + case paymentorder.FieldSubscriptionDays: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSubscriptionDays(v) + return nil + case paymentorder.FieldRefundAmount: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddRefundAmount(v) + return nil + } + return fmt.Errorf("unknown PaymentOrder numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *PaymentOrderMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(paymentorder.FieldUserNotes) { + fields = append(fields, paymentorder.FieldUserNotes) + } + if m.FieldCleared(paymentorder.FieldPayURL) { + fields = append(fields, paymentorder.FieldPayURL) + } + if m.FieldCleared(paymentorder.FieldQrCode) { + fields = append(fields, paymentorder.FieldQrCode) + } + if m.FieldCleared(paymentorder.FieldQrCodeImg) { + fields = append(fields, paymentorder.FieldQrCodeImg) + } + if m.FieldCleared(paymentorder.FieldPlanID) { + fields = append(fields, paymentorder.FieldPlanID) + } + if m.FieldCleared(paymentorder.FieldSubscriptionGroupID) { + fields = append(fields, paymentorder.FieldSubscriptionGroupID) + } + if m.FieldCleared(paymentorder.FieldSubscriptionDays) { + fields = append(fields, paymentorder.FieldSubscriptionDays) + } + if m.FieldCleared(paymentorder.FieldProviderInstanceID) { + fields = append(fields, paymentorder.FieldProviderInstanceID) + } + if m.FieldCleared(paymentorder.FieldRefundReason) { + fields = append(fields, paymentorder.FieldRefundReason) + } + if m.FieldCleared(paymentorder.FieldRefundAt) { + fields = append(fields, paymentorder.FieldRefundAt) + } + if m.FieldCleared(paymentorder.FieldRefundRequestedAt) { + fields = append(fields, paymentorder.FieldRefundRequestedAt) + } + if m.FieldCleared(paymentorder.FieldRefundRequestReason) { + fields = append(fields, paymentorder.FieldRefundRequestReason) + } + if m.FieldCleared(paymentorder.FieldRefundRequestedBy) { + fields = append(fields, paymentorder.FieldRefundRequestedBy) + } + if m.FieldCleared(paymentorder.FieldPaidAt) { + fields = append(fields, paymentorder.FieldPaidAt) + } + if m.FieldCleared(paymentorder.FieldCompletedAt) { + fields = append(fields, paymentorder.FieldCompletedAt) + } + if m.FieldCleared(paymentorder.FieldFailedAt) { + fields = append(fields, paymentorder.FieldFailedAt) + } + if m.FieldCleared(paymentorder.FieldFailedReason) { + fields = append(fields, paymentorder.FieldFailedReason) + } + if m.FieldCleared(paymentorder.FieldSrcURL) { + fields = append(fields, paymentorder.FieldSrcURL) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *PaymentOrderMutation) 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 *PaymentOrderMutation) ClearField(name string) error { + switch name { + case paymentorder.FieldUserNotes: + m.ClearUserNotes() + return nil + case paymentorder.FieldPayURL: + m.ClearPayURL() + return nil + case paymentorder.FieldQrCode: + m.ClearQrCode() + return nil + case paymentorder.FieldQrCodeImg: + m.ClearQrCodeImg() + return nil + case paymentorder.FieldPlanID: + m.ClearPlanID() + return nil + case paymentorder.FieldSubscriptionGroupID: + m.ClearSubscriptionGroupID() + return nil + case paymentorder.FieldSubscriptionDays: + m.ClearSubscriptionDays() + return nil + case paymentorder.FieldProviderInstanceID: + m.ClearProviderInstanceID() + return nil + case paymentorder.FieldRefundReason: + m.ClearRefundReason() + return nil + case paymentorder.FieldRefundAt: + m.ClearRefundAt() + return nil + case paymentorder.FieldRefundRequestedAt: + m.ClearRefundRequestedAt() + return nil + case paymentorder.FieldRefundRequestReason: + m.ClearRefundRequestReason() + return nil + case paymentorder.FieldRefundRequestedBy: + m.ClearRefundRequestedBy() + return nil + case paymentorder.FieldPaidAt: + m.ClearPaidAt() + return nil + case paymentorder.FieldCompletedAt: + m.ClearCompletedAt() + return nil + case paymentorder.FieldFailedAt: + m.ClearFailedAt() + return nil + case paymentorder.FieldFailedReason: + m.ClearFailedReason() + return nil + case paymentorder.FieldSrcURL: + m.ClearSrcURL() + return nil + } + return fmt.Errorf("unknown PaymentOrder 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 *PaymentOrderMutation) ResetField(name string) error { + switch name { + case paymentorder.FieldUserID: + m.ResetUserID() + return nil + case paymentorder.FieldUserEmail: + m.ResetUserEmail() + return nil + case paymentorder.FieldUserName: + m.ResetUserName() + return nil + case paymentorder.FieldUserNotes: + m.ResetUserNotes() + return nil + case paymentorder.FieldAmount: + m.ResetAmount() + return nil + case paymentorder.FieldPayAmount: + m.ResetPayAmount() + return nil + case paymentorder.FieldFeeRate: + m.ResetFeeRate() + return nil + case paymentorder.FieldRechargeCode: + m.ResetRechargeCode() + return nil + case paymentorder.FieldOutTradeNo: + m.ResetOutTradeNo() + return nil + case paymentorder.FieldPaymentType: + m.ResetPaymentType() + return nil + case paymentorder.FieldPaymentTradeNo: + m.ResetPaymentTradeNo() + return nil + case paymentorder.FieldPayURL: + m.ResetPayURL() + return nil + case paymentorder.FieldQrCode: + m.ResetQrCode() + return nil + case paymentorder.FieldQrCodeImg: + m.ResetQrCodeImg() + return nil + case paymentorder.FieldOrderType: + m.ResetOrderType() + return nil + case paymentorder.FieldPlanID: + m.ResetPlanID() + return nil + case paymentorder.FieldSubscriptionGroupID: + m.ResetSubscriptionGroupID() + return nil + case paymentorder.FieldSubscriptionDays: + m.ResetSubscriptionDays() + return nil + case paymentorder.FieldProviderInstanceID: + m.ResetProviderInstanceID() + return nil + case paymentorder.FieldStatus: + m.ResetStatus() + return nil + case paymentorder.FieldRefundAmount: + m.ResetRefundAmount() + return nil + case paymentorder.FieldRefundReason: + m.ResetRefundReason() + return nil + case paymentorder.FieldRefundAt: + m.ResetRefundAt() + return nil + case paymentorder.FieldForceRefund: + m.ResetForceRefund() + return nil + case paymentorder.FieldRefundRequestedAt: + m.ResetRefundRequestedAt() + return nil + case paymentorder.FieldRefundRequestReason: + m.ResetRefundRequestReason() + return nil + case paymentorder.FieldRefundRequestedBy: + m.ResetRefundRequestedBy() + return nil + case paymentorder.FieldExpiresAt: + m.ResetExpiresAt() + return nil + case paymentorder.FieldPaidAt: + m.ResetPaidAt() + return nil + case paymentorder.FieldCompletedAt: + m.ResetCompletedAt() + return nil + case paymentorder.FieldFailedAt: + m.ResetFailedAt() + return nil + case paymentorder.FieldFailedReason: + m.ResetFailedReason() + return nil + case paymentorder.FieldClientIP: + m.ResetClientIP() + return nil + case paymentorder.FieldSrcHost: + m.ResetSrcHost() + return nil + case paymentorder.FieldSrcURL: + m.ResetSrcURL() + return nil + case paymentorder.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case paymentorder.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown PaymentOrder field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *PaymentOrderMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, paymentorder.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *PaymentOrderMutation) AddedIDs(name string) []ent.Value { + switch name { + case paymentorder.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 *PaymentOrderMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *PaymentOrderMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *PaymentOrderMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, paymentorder.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *PaymentOrderMutation) EdgeCleared(name string) bool { + switch name { + case paymentorder.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 *PaymentOrderMutation) ClearEdge(name string) error { + switch name { + case paymentorder.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown PaymentOrder 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 *PaymentOrderMutation) ResetEdge(name string) error { + switch name { + case paymentorder.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown PaymentOrder edge %s", name) +} + +// PaymentProviderInstanceMutation represents an operation that mutates the PaymentProviderInstance nodes in the graph. +type PaymentProviderInstanceMutation struct { + config + op Op + typ string + id *int64 + provider_key *string + name *string + _config *string + supported_types *string + enabled *bool + payment_mode *string + sort_order *int + addsort_order *int + limits *string + refund_enabled *bool + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*PaymentProviderInstance, error) + predicates []predicate.PaymentProviderInstance +} + +var _ ent.Mutation = (*PaymentProviderInstanceMutation)(nil) + +// paymentproviderinstanceOption allows management of the mutation configuration using functional options. +type paymentproviderinstanceOption func(*PaymentProviderInstanceMutation) + +// newPaymentProviderInstanceMutation creates new mutation for the PaymentProviderInstance entity. +func newPaymentProviderInstanceMutation(c config, op Op, opts ...paymentproviderinstanceOption) *PaymentProviderInstanceMutation { + m := &PaymentProviderInstanceMutation{ + config: c, + op: op, + typ: TypePaymentProviderInstance, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withPaymentProviderInstanceID sets the ID field of the mutation. +func withPaymentProviderInstanceID(id int64) paymentproviderinstanceOption { + return func(m *PaymentProviderInstanceMutation) { + var ( + err error + once sync.Once + value *PaymentProviderInstance + ) + m.oldValue = func(ctx context.Context) (*PaymentProviderInstance, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().PaymentProviderInstance.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withPaymentProviderInstance sets the old PaymentProviderInstance of the mutation. +func withPaymentProviderInstance(node *PaymentProviderInstance) paymentproviderinstanceOption { + return func(m *PaymentProviderInstanceMutation) { + m.oldValue = func(context.Context) (*PaymentProviderInstance, 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 PaymentProviderInstanceMutation) 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 PaymentProviderInstanceMutation) 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 *PaymentProviderInstanceMutation) 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 *PaymentProviderInstanceMutation) 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().PaymentProviderInstance.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetProviderKey sets the "provider_key" field. +func (m *PaymentProviderInstanceMutation) SetProviderKey(s string) { + m.provider_key = &s +} + +// ProviderKey returns the value of the "provider_key" field in the mutation. +func (m *PaymentProviderInstanceMutation) ProviderKey() (r string, exists bool) { + v := m.provider_key + if v == nil { + return + } + return *v, true +} + +// OldProviderKey returns the old "provider_key" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldProviderKey(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProviderKey is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProviderKey requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProviderKey: %w", err) + } + return oldValue.ProviderKey, nil +} + +// ResetProviderKey resets all changes to the "provider_key" field. +func (m *PaymentProviderInstanceMutation) ResetProviderKey() { + m.provider_key = nil +} + +// SetName sets the "name" field. +func (m *PaymentProviderInstanceMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *PaymentProviderInstanceMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *PaymentProviderInstanceMutation) ResetName() { + m.name = nil +} + +// SetConfig sets the "config" field. +func (m *PaymentProviderInstanceMutation) SetConfig(s string) { + m._config = &s +} + +// Config returns the value of the "config" field in the mutation. +func (m *PaymentProviderInstanceMutation) Config() (r string, exists bool) { + v := m._config + if v == nil { + return + } + return *v, true +} + +// OldConfig returns the old "config" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldConfig(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldConfig is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldConfig requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldConfig: %w", err) + } + return oldValue.Config, nil +} + +// ResetConfig resets all changes to the "config" field. +func (m *PaymentProviderInstanceMutation) ResetConfig() { + m._config = nil +} + +// SetSupportedTypes sets the "supported_types" field. +func (m *PaymentProviderInstanceMutation) SetSupportedTypes(s string) { + m.supported_types = &s +} + +// SupportedTypes returns the value of the "supported_types" field in the mutation. +func (m *PaymentProviderInstanceMutation) SupportedTypes() (r string, exists bool) { + v := m.supported_types + if v == nil { + return + } + return *v, true +} + +// OldSupportedTypes returns the old "supported_types" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldSupportedTypes(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSupportedTypes is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSupportedTypes requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSupportedTypes: %w", err) + } + return oldValue.SupportedTypes, nil +} + +// ResetSupportedTypes resets all changes to the "supported_types" field. +func (m *PaymentProviderInstanceMutation) ResetSupportedTypes() { + m.supported_types = nil +} + +// SetEnabled sets the "enabled" field. +func (m *PaymentProviderInstanceMutation) SetEnabled(b bool) { + m.enabled = &b +} + +// Enabled returns the value of the "enabled" field in the mutation. +func (m *PaymentProviderInstanceMutation) Enabled() (r bool, exists bool) { + v := m.enabled + if v == nil { + return + } + return *v, true +} + +// OldEnabled returns the old "enabled" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldEnabled(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldEnabled is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldEnabled requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldEnabled: %w", err) + } + return oldValue.Enabled, nil +} + +// ResetEnabled resets all changes to the "enabled" field. +func (m *PaymentProviderInstanceMutation) ResetEnabled() { + m.enabled = nil +} + +// SetPaymentMode sets the "payment_mode" field. +func (m *PaymentProviderInstanceMutation) SetPaymentMode(s string) { + m.payment_mode = &s +} + +// PaymentMode returns the value of the "payment_mode" field in the mutation. +func (m *PaymentProviderInstanceMutation) PaymentMode() (r string, exists bool) { + v := m.payment_mode + if v == nil { + return + } + return *v, true +} + +// OldPaymentMode returns the old "payment_mode" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldPaymentMode(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldPaymentMode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldPaymentMode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldPaymentMode: %w", err) + } + return oldValue.PaymentMode, nil +} + +// ResetPaymentMode resets all changes to the "payment_mode" field. +func (m *PaymentProviderInstanceMutation) ResetPaymentMode() { + m.payment_mode = nil +} + +// SetSortOrder sets the "sort_order" field. +func (m *PaymentProviderInstanceMutation) SetSortOrder(i int) { + m.sort_order = &i + m.addsort_order = nil +} + +// SortOrder returns the value of the "sort_order" field in the mutation. +func (m *PaymentProviderInstanceMutation) SortOrder() (r int, exists bool) { + v := m.sort_order + if v == nil { + return + } + return *v, true +} + +// OldSortOrder returns the old "sort_order" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldSortOrder(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSortOrder is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSortOrder requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSortOrder: %w", err) + } + return oldValue.SortOrder, nil +} + +// AddSortOrder adds i to the "sort_order" field. +func (m *PaymentProviderInstanceMutation) AddSortOrder(i int) { + if m.addsort_order != nil { + *m.addsort_order += i + } else { + m.addsort_order = &i + } +} + +// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation. +func (m *PaymentProviderInstanceMutation) AddedSortOrder() (r int, exists bool) { + v := m.addsort_order + if v == nil { + return + } + return *v, true +} + +// ResetSortOrder resets all changes to the "sort_order" field. +func (m *PaymentProviderInstanceMutation) ResetSortOrder() { + m.sort_order = nil + m.addsort_order = nil +} + +// SetLimits sets the "limits" field. +func (m *PaymentProviderInstanceMutation) SetLimits(s string) { + m.limits = &s +} + +// Limits returns the value of the "limits" field in the mutation. +func (m *PaymentProviderInstanceMutation) Limits() (r string, exists bool) { + v := m.limits + if v == nil { + return + } + return *v, true +} + +// OldLimits returns the old "limits" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldLimits(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLimits is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLimits requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLimits: %w", err) + } + return oldValue.Limits, nil +} + +// ResetLimits resets all changes to the "limits" field. +func (m *PaymentProviderInstanceMutation) ResetLimits() { + m.limits = nil +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (m *PaymentProviderInstanceMutation) SetRefundEnabled(b bool) { + m.refund_enabled = &b +} + +// RefundEnabled returns the value of the "refund_enabled" field in the mutation. +func (m *PaymentProviderInstanceMutation) RefundEnabled() (r bool, exists bool) { + v := m.refund_enabled + if v == nil { + return + } + return *v, true +} + +// OldRefundEnabled returns the old "refund_enabled" field's value of the PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) OldRefundEnabled(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRefundEnabled is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRefundEnabled requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRefundEnabled: %w", err) + } + return oldValue.RefundEnabled, nil +} + +// ResetRefundEnabled resets all changes to the "refund_enabled" field. +func (m *PaymentProviderInstanceMutation) ResetRefundEnabled() { + m.refund_enabled = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *PaymentProviderInstanceMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *PaymentProviderInstanceMutation) 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 PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) 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 *PaymentProviderInstanceMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *PaymentProviderInstanceMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *PaymentProviderInstanceMutation) 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 PaymentProviderInstance entity. +// If the PaymentProviderInstance 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 *PaymentProviderInstanceMutation) 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 *PaymentProviderInstanceMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// Where appends a list predicates to the PaymentProviderInstanceMutation builder. +func (m *PaymentProviderInstanceMutation) Where(ps ...predicate.PaymentProviderInstance) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the PaymentProviderInstanceMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *PaymentProviderInstanceMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.PaymentProviderInstance, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *PaymentProviderInstanceMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *PaymentProviderInstanceMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (PaymentProviderInstance). +func (m *PaymentProviderInstanceMutation) 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 *PaymentProviderInstanceMutation) Fields() []string { + fields := make([]string, 0, 11) + if m.provider_key != nil { + fields = append(fields, paymentproviderinstance.FieldProviderKey) + } + if m.name != nil { + fields = append(fields, paymentproviderinstance.FieldName) + } + if m._config != nil { + fields = append(fields, paymentproviderinstance.FieldConfig) + } + if m.supported_types != nil { + fields = append(fields, paymentproviderinstance.FieldSupportedTypes) + } + if m.enabled != nil { + fields = append(fields, paymentproviderinstance.FieldEnabled) + } + if m.payment_mode != nil { + fields = append(fields, paymentproviderinstance.FieldPaymentMode) + } + if m.sort_order != nil { + fields = append(fields, paymentproviderinstance.FieldSortOrder) + } + if m.limits != nil { + fields = append(fields, paymentproviderinstance.FieldLimits) + } + if m.refund_enabled != nil { + fields = append(fields, paymentproviderinstance.FieldRefundEnabled) + } + if m.created_at != nil { + fields = append(fields, paymentproviderinstance.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, paymentproviderinstance.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 *PaymentProviderInstanceMutation) Field(name string) (ent.Value, bool) { + switch name { + case paymentproviderinstance.FieldProviderKey: + return m.ProviderKey() + case paymentproviderinstance.FieldName: + return m.Name() + case paymentproviderinstance.FieldConfig: + return m.Config() + case paymentproviderinstance.FieldSupportedTypes: + return m.SupportedTypes() + case paymentproviderinstance.FieldEnabled: + return m.Enabled() + case paymentproviderinstance.FieldPaymentMode: + return m.PaymentMode() + case paymentproviderinstance.FieldSortOrder: + return m.SortOrder() + case paymentproviderinstance.FieldLimits: + return m.Limits() + case paymentproviderinstance.FieldRefundEnabled: + return m.RefundEnabled() + case paymentproviderinstance.FieldCreatedAt: + return m.CreatedAt() + case paymentproviderinstance.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 *PaymentProviderInstanceMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case paymentproviderinstance.FieldProviderKey: + return m.OldProviderKey(ctx) + case paymentproviderinstance.FieldName: + return m.OldName(ctx) + case paymentproviderinstance.FieldConfig: + return m.OldConfig(ctx) + case paymentproviderinstance.FieldSupportedTypes: + return m.OldSupportedTypes(ctx) + case paymentproviderinstance.FieldEnabled: + return m.OldEnabled(ctx) + case paymentproviderinstance.FieldPaymentMode: + return m.OldPaymentMode(ctx) + case paymentproviderinstance.FieldSortOrder: + return m.OldSortOrder(ctx) + case paymentproviderinstance.FieldLimits: + return m.OldLimits(ctx) + case paymentproviderinstance.FieldRefundEnabled: + return m.OldRefundEnabled(ctx) + case paymentproviderinstance.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case paymentproviderinstance.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown PaymentProviderInstance 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 *PaymentProviderInstanceMutation) SetField(name string, value ent.Value) error { + switch name { + case paymentproviderinstance.FieldProviderKey: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProviderKey(v) + return nil + case paymentproviderinstance.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + case paymentproviderinstance.FieldConfig: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetConfig(v) + return nil + case paymentproviderinstance.FieldSupportedTypes: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSupportedTypes(v) + return nil + case paymentproviderinstance.FieldEnabled: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetEnabled(v) + return nil + case paymentproviderinstance.FieldPaymentMode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPaymentMode(v) + return nil + case paymentproviderinstance.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSortOrder(v) + return nil + case paymentproviderinstance.FieldLimits: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLimits(v) + return nil + case paymentproviderinstance.FieldRefundEnabled: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRefundEnabled(v) + return nil + case paymentproviderinstance.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 paymentproviderinstance.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 PaymentProviderInstance field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *PaymentProviderInstanceMutation) AddedFields() []string { + var fields []string + if m.addsort_order != nil { + fields = append(fields, paymentproviderinstance.FieldSortOrder) + } + 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 *PaymentProviderInstanceMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case paymentproviderinstance.FieldSortOrder: + return m.AddedSortOrder() + } + 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 *PaymentProviderInstanceMutation) AddField(name string, value ent.Value) error { + switch name { + case paymentproviderinstance.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSortOrder(v) + return nil + } + return fmt.Errorf("unknown PaymentProviderInstance numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *PaymentProviderInstanceMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *PaymentProviderInstanceMutation) 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 *PaymentProviderInstanceMutation) ClearField(name string) error { + return fmt.Errorf("unknown PaymentProviderInstance 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 *PaymentProviderInstanceMutation) ResetField(name string) error { + switch name { + case paymentproviderinstance.FieldProviderKey: + m.ResetProviderKey() + return nil + case paymentproviderinstance.FieldName: + m.ResetName() + return nil + case paymentproviderinstance.FieldConfig: + m.ResetConfig() + return nil + case paymentproviderinstance.FieldSupportedTypes: + m.ResetSupportedTypes() + return nil + case paymentproviderinstance.FieldEnabled: + m.ResetEnabled() + return nil + case paymentproviderinstance.FieldPaymentMode: + m.ResetPaymentMode() + return nil + case paymentproviderinstance.FieldSortOrder: + m.ResetSortOrder() + return nil + case paymentproviderinstance.FieldLimits: + m.ResetLimits() + return nil + case paymentproviderinstance.FieldRefundEnabled: + m.ResetRefundEnabled() + return nil + case paymentproviderinstance.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case paymentproviderinstance.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown PaymentProviderInstance field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *PaymentProviderInstanceMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *PaymentProviderInstanceMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *PaymentProviderInstanceMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *PaymentProviderInstanceMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *PaymentProviderInstanceMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *PaymentProviderInstanceMutation) EdgeCleared(name string) bool { + 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 *PaymentProviderInstanceMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown PaymentProviderInstance 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 *PaymentProviderInstanceMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown PaymentProviderInstance 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) + 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 promocode.FieldUpdatedAt: - v, ok := value.(time.Time) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) + 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} } - m.SetUpdatedAt(v) + 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 PromoCode field %s", name) + return fmt.Errorf("unknown PromoCodeUsage edge %s", name) +} + +// ProxyMutation represents an operation that mutates the Proxy nodes in the graph. +type ProxyMutation struct { + config + op Op + typ string + id *int64 + created_at *time.Time + updated_at *time.Time + deleted_at *time.Time + name *string + protocol *string + host *string + port *int + addport *int + username *string + password *string + status *string + clearedFields map[string]struct{} + accounts map[int64]struct{} + removedaccounts map[int64]struct{} + clearedaccounts bool + done bool + oldValue func(context.Context) (*Proxy, error) + predicates []predicate.Proxy +} + +var _ ent.Mutation = (*ProxyMutation)(nil) + +// proxyOption allows management of the mutation configuration using functional options. +type proxyOption func(*ProxyMutation) + +// newProxyMutation creates new mutation for the Proxy entity. +func newProxyMutation(c config, op Op, opts ...proxyOption) *ProxyMutation { + m := &ProxyMutation{ + config: c, + op: op, + typ: TypeProxy, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withProxyID sets the ID field of the mutation. +func withProxyID(id int64) proxyOption { + return func(m *ProxyMutation) { + var ( + err error + once sync.Once + value *Proxy + ) + m.oldValue = func(ctx context.Context) (*Proxy, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Proxy.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withProxy sets the old Proxy of the mutation. +func withProxy(node *Proxy) proxyOption { + return func(m *ProxyMutation) { + m.oldValue = func(context.Context) (*Proxy, 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 ProxyMutation) 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 ProxyMutation) 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 *ProxyMutation) 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 *ProxyMutation) 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().Proxy.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCreatedAt sets the "created_at" field. +func (m *ProxyMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *ProxyMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true } -// 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) +// OldCreatedAt returns the old "created_at" field's value of the Proxy entity. +// If the Proxy 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 *ProxyMutation) 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.addmax_uses != nil { - fields = append(fields, promocode.FieldMaxUses) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") } - if m.addused_count != nil { - fields = append(fields, promocode.FieldUsedCount) + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } - return fields + return oldValue.CreatedAt, nil } -// 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 +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *ProxyMutation) ResetCreatedAt() { + m.created_at = nil } -// 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 +// SetUpdatedAt sets the "updated_at" field. +func (m *ProxyMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *ProxyMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return } - return fmt.Errorf("unknown PromoCode numeric field %s", name) + return *v, true } -// 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) +// OldUpdatedAt returns the old "updated_at" field's value of the Proxy entity. +// If the Proxy 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 *ProxyMutation) 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.FieldCleared(promocode.FieldNotes) { - fields = append(fields, promocode.FieldNotes) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") } - return fields + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil } -// 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 +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *ProxyMutation) ResetUpdatedAt() { + m.updated_at = nil } -// 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) +// SetDeletedAt sets the "deleted_at" field. +func (m *ProxyMutation) SetDeletedAt(t time.Time) { + m.deleted_at = &t } -// 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 +// DeletedAt returns the value of the "deleted_at" field in the mutation. +func (m *ProxyMutation) DeletedAt() (r time.Time, exists bool) { + v := m.deleted_at + if v == nil { + return } - return fmt.Errorf("unknown PromoCode field %s", name) + return *v, true } -// 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) +// OldDeletedAt returns the old "deleted_at" field's value of the Proxy entity. +// If the Proxy 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 *ProxyMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations") } - 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 + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDeletedAt requires an ID field in the mutation") } - return nil + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err) + } + return oldValue.DeletedAt, 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 +// ClearDeletedAt clears the value of the "deleted_at" field. +func (m *ProxyMutation) ClearDeletedAt() { + m.deleted_at = nil + m.clearedFields[proxy.FieldDeletedAt] = struct{}{} } -// 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 +// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation. +func (m *ProxyMutation) DeletedAtCleared() bool { + _, ok := m.clearedFields[proxy.FieldDeletedAt] + return ok } -// 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 +// ResetDeletedAt resets all changes to the "deleted_at" field. +func (m *ProxyMutation) ResetDeletedAt() { + m.deleted_at = nil + delete(m.clearedFields, proxy.FieldDeletedAt) } -// 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 +// SetName sets the "name" field. +func (m *ProxyMutation) SetName(s string) { + m.name = &s } -// 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 { +// Name returns the value of the "name" field in the mutation. +func (m *ProxyMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return } - return fmt.Errorf("unknown PromoCode unique edge %s", name) + return *v, true } -// 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 +// OldName returns the old "name" field's value of the Proxy entity. +// If the Proxy 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 *ProxyMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") } - return fmt.Errorf("unknown PromoCode edge %s", name) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil } -// 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 +// ResetName resets all changes to the "name" field. +func (m *ProxyMutation) ResetName() { + m.name = nil } -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 +// SetProtocol sets the "protocol" field. +func (m *ProxyMutation) SetProtocol(s string) { + m.protocol = &s } -// 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 +// Protocol returns the value of the "protocol" field in the mutation. +func (m *ProxyMutation) Protocol() (r string, exists bool) { + v := m.protocol + if v == nil { + return } + return *v, true } -// 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 +// OldProtocol returns the old "protocol" field's value of the Proxy entity. +// If the Proxy 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 *ProxyMutation) OldProtocol(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProtocol is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProtocol requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProtocol: %w", err) } + return oldValue.Protocol, nil } -// 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 +// ResetProtocol resets all changes to the "protocol" field. +func (m *ProxyMutation) ResetProtocol() { + m.protocol = nil } -// 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 +// SetHost sets the "host" field. +func (m *ProxyMutation) SetHost(s string) { + m.host = &s } -// 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 { +// Host returns the value of the "host" field in the mutation. +func (m *ProxyMutation) Host() (r string, exists bool) { + v := m.host + if v == nil { return } - return *m.id, true + return *v, 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) +// OldHost returns the old "host" field's value of the Proxy entity. +// If the Proxy 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 *ProxyMutation) OldHost(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldHost is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldHost requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldHost: %w", err) } + return oldValue.Host, nil } -// SetPromoCodeID sets the "promo_code_id" field. -func (m *PromoCodeUsageMutation) SetPromoCodeID(i int64) { - m.promo_code = &i +// ResetHost resets all changes to the "host" field. +func (m *ProxyMutation) ResetHost() { + m.host = nil } -// 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 +// SetPort sets the "port" field. +func (m *ProxyMutation) SetPort(i int) { + m.port = &i + m.addport = nil +} + +// Port returns the value of the "port" field in the mutation. +func (m *ProxyMutation) Port() (r int, exists bool) { + v := m.port 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. +// OldPort returns the old "port" field's value of the Proxy entity. +// If the Proxy 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) { +func (m *ProxyMutation) OldPort(ctx context.Context) (v int, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldPromoCodeID is only allowed on UpdateOne operations") + return v, errors.New("OldPort 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") + return v, errors.New("OldPort 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 v, fmt.Errorf("querying old value for OldPort: %w", err) } - return oldValue.PromoCodeID, nil + return oldValue.Port, nil } -// ResetPromoCodeID resets all changes to the "promo_code_id" field. -func (m *PromoCodeUsageMutation) ResetPromoCodeID() { - m.promo_code = nil +// AddPort adds i to the "port" field. +func (m *ProxyMutation) AddPort(i int) { + if m.addport != nil { + *m.addport += i + } else { + m.addport = &i + } } -// SetUserID sets the "user_id" field. -func (m *PromoCodeUsageMutation) SetUserID(i int64) { - m.user = &i +// AddedPort returns the value that was added to the "port" field in this mutation. +func (m *ProxyMutation) AddedPort() (r int, exists bool) { + v := m.addport + if v == nil { + return + } + return *v, true } -// UserID returns the value of the "user_id" field in the mutation. -func (m *PromoCodeUsageMutation) UserID() (r int64, exists bool) { - v := m.user +// ResetPort resets all changes to the "port" field. +func (m *ProxyMutation) ResetPort() { + m.port = nil + m.addport = nil +} + +// SetUsername sets the "username" field. +func (m *ProxyMutation) SetUsername(s string) { + m.username = &s +} + +// Username returns the value of the "username" field in the mutation. +func (m *ProxyMutation) Username() (r string, exists bool) { + v := m.username 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. +// OldUsername returns the old "username" field's value of the Proxy entity. +// If the Proxy 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) { +func (m *ProxyMutation) OldUsername(ctx context.Context) (v *string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUserID is only allowed on UpdateOne operations") + return v, errors.New("OldUsername 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") + return v, errors.New("OldUsername 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 v, fmt.Errorf("querying old value for OldUsername: %w", err) } - return oldValue.UserID, nil + return oldValue.Username, nil +} + +// ClearUsername clears the value of the "username" field. +func (m *ProxyMutation) ClearUsername() { + m.username = nil + m.clearedFields[proxy.FieldUsername] = struct{}{} } -// ResetUserID resets all changes to the "user_id" field. -func (m *PromoCodeUsageMutation) ResetUserID() { - m.user = nil +// UsernameCleared returns if the "username" field was cleared in this mutation. +func (m *ProxyMutation) UsernameCleared() bool { + _, ok := m.clearedFields[proxy.FieldUsername] + return ok } -// SetBonusAmount sets the "bonus_amount" field. -func (m *PromoCodeUsageMutation) SetBonusAmount(f float64) { - m.bonus_amount = &f - m.addbonus_amount = nil +// ResetUsername resets all changes to the "username" field. +func (m *ProxyMutation) ResetUsername() { + m.username = nil + delete(m.clearedFields, proxy.FieldUsername) } -// BonusAmount returns the value of the "bonus_amount" field in the mutation. -func (m *PromoCodeUsageMutation) BonusAmount() (r float64, exists bool) { - v := m.bonus_amount +// SetPassword sets the "password" field. +func (m *ProxyMutation) SetPassword(s string) { + m.password = &s +} + +// Password returns the value of the "password" field in the mutation. +func (m *ProxyMutation) Password() (r string, exists bool) { + v := m.password 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. +// OldPassword returns the old "password" field's value of the Proxy entity. +// If the Proxy 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) { +func (m *ProxyMutation) OldPassword(ctx context.Context) (v *string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldBonusAmount is only allowed on UpdateOne operations") + return v, errors.New("OldPassword 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") + return v, errors.New("OldPassword 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 v, fmt.Errorf("querying old value for OldPassword: %w", err) } - return oldValue.BonusAmount, nil + return oldValue.Password, 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 - } +// ClearPassword clears the value of the "password" field. +func (m *ProxyMutation) ClearPassword() { + m.password = nil + m.clearedFields[proxy.FieldPassword] = struct{}{} } -// 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 +// PasswordCleared returns if the "password" field was cleared in this mutation. +func (m *ProxyMutation) PasswordCleared() bool { + _, ok := m.clearedFields[proxy.FieldPassword] + return ok } -// ResetBonusAmount resets all changes to the "bonus_amount" field. -func (m *PromoCodeUsageMutation) ResetBonusAmount() { - m.bonus_amount = nil - m.addbonus_amount = nil +// ResetPassword resets all changes to the "password" field. +func (m *ProxyMutation) ResetPassword() { + m.password = nil + delete(m.clearedFields, proxy.FieldPassword) } -// SetUsedAt sets the "used_at" field. -func (m *PromoCodeUsageMutation) SetUsedAt(t time.Time) { - m.used_at = &t +// SetStatus sets the "status" field. +func (m *ProxyMutation) SetStatus(s string) { + m.status = &s } -// 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 +// Status returns the value of the "status" field in the mutation. +func (m *ProxyMutation) Status() (r string, exists bool) { + v := m.status 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. +// OldStatus returns the old "status" field's value of the Proxy entity. +// If the Proxy 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) { +func (m *ProxyMutation) OldStatus(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsedAt is only allowed on UpdateOne operations") + return v, errors.New("OldStatus 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") + 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 OldUsedAt: %w", err) + return v, fmt.Errorf("querying old value for OldStatus: %w", err) } - return oldValue.UsedAt, nil -} - -// ResetUsedAt resets all changes to the "used_at" field. -func (m *PromoCodeUsageMutation) ResetUsedAt() { - m.used_at = nil + return oldValue.Status, nil } -// ClearPromoCode clears the "promo_code" edge to the PromoCode entity. -func (m *PromoCodeUsageMutation) ClearPromoCode() { - m.clearedpromo_code = true - m.clearedFields[promocodeusage.FieldPromoCodeID] = struct{}{} +// ResetStatus resets all changes to the "status" field. +func (m *ProxyMutation) ResetStatus() { + m.status = nil } -// PromoCodeCleared reports if the "promo_code" edge to the PromoCode entity was cleared. -func (m *PromoCodeUsageMutation) PromoCodeCleared() bool { - return m.clearedpromo_code +// AddAccountIDs adds the "accounts" edge to the Account entity by ids. +func (m *ProxyMutation) AddAccountIDs(ids ...int64) { + if m.accounts == nil { + m.accounts = make(map[int64]struct{}) + } + for i := range ids { + m.accounts[ids[i]] = struct{}{} + } } -// 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 +// ClearAccounts clears the "accounts" edge to the Account entity. +func (m *ProxyMutation) ClearAccounts() { + m.clearedaccounts = true } -// ResetPromoCode resets all changes to the "promo_code" edge. -func (m *PromoCodeUsageMutation) ResetPromoCode() { - m.promo_code = nil - m.clearedpromo_code = false +// AccountsCleared reports if the "accounts" edge to the Account entity was cleared. +func (m *ProxyMutation) AccountsCleared() bool { + return m.clearedaccounts } -// ClearUser clears the "user" edge to the User entity. -func (m *PromoCodeUsageMutation) ClearUser() { - m.cleareduser = true - m.clearedFields[promocodeusage.FieldUserID] = struct{}{} +// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs. +func (m *ProxyMutation) RemoveAccountIDs(ids ...int64) { + if m.removedaccounts == nil { + m.removedaccounts = make(map[int64]struct{}) + } + for i := range ids { + delete(m.accounts, ids[i]) + m.removedaccounts[ids[i]] = struct{}{} + } } -// UserCleared reports if the "user" edge to the User entity was cleared. -func (m *PromoCodeUsageMutation) UserCleared() bool { - return m.cleareduser +// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity. +func (m *ProxyMutation) RemovedAccountsIDs() (ids []int64) { + for id := range m.removedaccounts { + ids = append(ids, id) + } + return } -// 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) +// AccountsIDs returns the "accounts" edge IDs in the mutation. +func (m *ProxyMutation) AccountsIDs() (ids []int64) { + for id := range m.accounts { + ids = append(ids, id) } return } -// ResetUser resets all changes to the "user" edge. -func (m *PromoCodeUsageMutation) ResetUser() { - m.user = nil - m.cleareduser = false +// ResetAccounts resets all changes to the "accounts" edge. +func (m *ProxyMutation) ResetAccounts() { + m.accounts = nil + m.clearedaccounts = false + m.removedaccounts = nil } -// Where appends a list predicates to the PromoCodeUsageMutation builder. -func (m *PromoCodeUsageMutation) Where(ps ...predicate.PromoCodeUsage) { +// Where appends a list predicates to the ProxyMutation builder. +func (m *ProxyMutation) Where(ps ...predicate.Proxy) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the PromoCodeUsageMutation builder. Using this method, +// WhereP appends storage-level predicates to the ProxyMutation 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)) +func (m *ProxyMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Proxy, len(ps)) for i := range ps { p[i] = ps[i] } @@ -13474,36 +18772,54 @@ func (m *PromoCodeUsageMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *PromoCodeUsageMutation) Op() Op { +func (m *ProxyMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *PromoCodeUsageMutation) SetOp(op Op) { +func (m *ProxyMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (PromoCodeUsage). -func (m *PromoCodeUsageMutation) Type() string { +// Type returns the node type of this mutation (Proxy). +func (m *ProxyMutation) 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) +func (m *ProxyMutation) Fields() []string { + fields := make([]string, 0, 10) + if m.created_at != nil { + fields = append(fields, proxy.FieldCreatedAt) } - if m.user != nil { - fields = append(fields, promocodeusage.FieldUserID) + if m.updated_at != nil { + fields = append(fields, proxy.FieldUpdatedAt) } - if m.bonus_amount != nil { - fields = append(fields, promocodeusage.FieldBonusAmount) + if m.deleted_at != nil { + fields = append(fields, proxy.FieldDeletedAt) } - if m.used_at != nil { - fields = append(fields, promocodeusage.FieldUsedAt) + if m.name != nil { + fields = append(fields, proxy.FieldName) + } + if m.protocol != nil { + fields = append(fields, proxy.FieldProtocol) + } + if m.host != nil { + fields = append(fields, proxy.FieldHost) + } + if m.port != nil { + fields = append(fields, proxy.FieldPort) + } + if m.username != nil { + fields = append(fields, proxy.FieldUsername) + } + if m.password != nil { + fields = append(fields, proxy.FieldPassword) + } + if m.status != nil { + fields = append(fields, proxy.FieldStatus) } return fields } @@ -13511,16 +18827,28 @@ func (m *PromoCodeUsageMutation) Fields() []string { // 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) { +func (m *ProxyMutation) 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() + case proxy.FieldCreatedAt: + return m.CreatedAt() + case proxy.FieldUpdatedAt: + return m.UpdatedAt() + case proxy.FieldDeletedAt: + return m.DeletedAt() + case proxy.FieldName: + return m.Name() + case proxy.FieldProtocol: + return m.Protocol() + case proxy.FieldHost: + return m.Host() + case proxy.FieldPort: + return m.Port() + case proxy.FieldUsername: + return m.Username() + case proxy.FieldPassword: + return m.Password() + case proxy.FieldStatus: + return m.Status() } return nil, false } @@ -13528,63 +18856,117 @@ func (m *PromoCodeUsageMutation) Field(name string) (ent.Value, bool) { // 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) { +func (m *ProxyMutation) 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) + case proxy.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case proxy.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case proxy.FieldDeletedAt: + return m.OldDeletedAt(ctx) + case proxy.FieldName: + return m.OldName(ctx) + case proxy.FieldProtocol: + return m.OldProtocol(ctx) + case proxy.FieldHost: + return m.OldHost(ctx) + case proxy.FieldPort: + return m.OldPort(ctx) + case proxy.FieldUsername: + return m.OldUsername(ctx) + case proxy.FieldPassword: + return m.OldPassword(ctx) + case proxy.FieldStatus: + return m.OldStatus(ctx) } - return nil, fmt.Errorf("unknown PromoCodeUsage field %s", name) + return nil, fmt.Errorf("unknown Proxy 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 { +func (m *ProxyMutation) SetField(name string, value ent.Value) error { switch name { - case promocodeusage.FieldPromoCodeID: - v, ok := value.(int64) + case proxy.FieldCreatedAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetPromoCodeID(v) + m.SetCreatedAt(v) return nil - case promocodeusage.FieldUserID: - v, ok := value.(int64) + case proxy.FieldUpdatedAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetUserID(v) + m.SetUpdatedAt(v) return nil - case promocodeusage.FieldBonusAmount: - v, ok := value.(float64) + case proxy.FieldDeletedAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetBonusAmount(v) + m.SetDeletedAt(v) return nil - case promocodeusage.FieldUsedAt: - v, ok := value.(time.Time) + case proxy.FieldName: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetUsedAt(v) + m.SetName(v) + return nil + case proxy.FieldProtocol: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProtocol(v) + return nil + case proxy.FieldHost: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetHost(v) + return nil + case proxy.FieldPort: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPort(v) + return nil + case proxy.FieldUsername: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUsername(v) + return nil + case proxy.FieldPassword: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPassword(v) + return nil + case proxy.FieldStatus: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStatus(v) return nil } - return fmt.Errorf("unknown PromoCodeUsage field %s", name) + return fmt.Errorf("unknown Proxy field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *PromoCodeUsageMutation) AddedFields() []string { +func (m *ProxyMutation) AddedFields() []string { var fields []string - if m.addbonus_amount != nil { - fields = append(fields, promocodeusage.FieldBonusAmount) + if m.addport != nil { + fields = append(fields, proxy.FieldPort) } return fields } @@ -13592,10 +18974,10 @@ func (m *PromoCodeUsageMutation) AddedFields() []string { // 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) { +func (m *ProxyMutation) AddedField(name string) (ent.Value, bool) { switch name { - case promocodeusage.FieldBonusAmount: - return m.AddedBonusAmount() + case proxy.FieldPort: + return m.AddedPort() } return nil, false } @@ -13603,187 +18985,218 @@ func (m *PromoCodeUsageMutation) AddedField(name string) (ent.Value, bool) { // 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 { +func (m *ProxyMutation) AddField(name string, value ent.Value) error { switch name { - case promocodeusage.FieldBonusAmount: - v, ok := value.(float64) + case proxy.FieldPort: + v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.AddBonusAmount(v) + m.AddPort(v) return nil } - return fmt.Errorf("unknown PromoCodeUsage numeric field %s", name) + return fmt.Errorf("unknown Proxy numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *PromoCodeUsageMutation) ClearedFields() []string { - return nil +func (m *ProxyMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(proxy.FieldDeletedAt) { + fields = append(fields, proxy.FieldDeletedAt) + } + if m.FieldCleared(proxy.FieldUsername) { + fields = append(fields, proxy.FieldUsername) + } + if m.FieldCleared(proxy.FieldPassword) { + fields = append(fields, proxy.FieldPassword) + } + return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *PromoCodeUsageMutation) FieldCleared(name string) bool { +func (m *ProxyMutation) 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) +func (m *ProxyMutation) ClearField(name string) error { + switch name { + case proxy.FieldDeletedAt: + m.ClearDeletedAt() + return nil + case proxy.FieldUsername: + m.ClearUsername() + return nil + case proxy.FieldPassword: + m.ClearPassword() + return nil + } + return fmt.Errorf("unknown Proxy 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 { +func (m *ProxyMutation) ResetField(name string) error { switch name { - case promocodeusage.FieldPromoCodeID: - m.ResetPromoCodeID() + case proxy.FieldCreatedAt: + m.ResetCreatedAt() return nil - case promocodeusage.FieldUserID: - m.ResetUserID() + case proxy.FieldUpdatedAt: + m.ResetUpdatedAt() return nil - case promocodeusage.FieldBonusAmount: - m.ResetBonusAmount() + case proxy.FieldDeletedAt: + m.ResetDeletedAt() return nil - case promocodeusage.FieldUsedAt: - m.ResetUsedAt() + case proxy.FieldName: + m.ResetName() + return nil + case proxy.FieldProtocol: + m.ResetProtocol() + return nil + case proxy.FieldHost: + m.ResetHost() + return nil + case proxy.FieldPort: + m.ResetPort() + return nil + case proxy.FieldUsername: + m.ResetUsername() + return nil + case proxy.FieldPassword: + m.ResetPassword() + return nil + case proxy.FieldStatus: + m.ResetStatus() return nil } - return fmt.Errorf("unknown PromoCodeUsage field %s", name) + return fmt.Errorf("unknown Proxy 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) +func (m *ProxyMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.accounts != nil { + edges = append(edges, proxy.EdgeAccounts) } 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 { +func (m *ProxyMutation) 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} + case proxy.EdgeAccounts: + ids := make([]ent.Value, 0, len(m.accounts)) + for id := range m.accounts { + ids = append(ids, id) } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *PromoCodeUsageMutation) RemovedEdges() []string { - edges := make([]string, 0, 2) +func (m *ProxyMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + if m.removedaccounts != nil { + edges = append(edges, proxy.EdgeAccounts) + } 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 { +func (m *ProxyMutation) RemovedIDs(name string) []ent.Value { + switch name { + case proxy.EdgeAccounts: + ids := make([]ent.Value, 0, len(m.removedaccounts)) + for id := range m.removedaccounts { + ids = append(ids, id) + } + return ids + } 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) +func (m *ProxyMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedaccounts { + edges = append(edges, proxy.EdgeAccounts) } 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 { +func (m *ProxyMutation) EdgeCleared(name string) bool { switch name { - case promocodeusage.EdgePromoCode: - return m.clearedpromo_code - case promocodeusage.EdgeUser: - return m.cleareduser + case proxy.EdgeAccounts: + return m.clearedaccounts } 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 { +func (m *ProxyMutation) 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) + return fmt.Errorf("unknown Proxy 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 { +func (m *ProxyMutation) ResetEdge(name string) error { switch name { - case promocodeusage.EdgePromoCode: - m.ResetPromoCode() - return nil - case promocodeusage.EdgeUser: - m.ResetUser() + case proxy.EdgeAccounts: + m.ResetAccounts() return nil } - return fmt.Errorf("unknown PromoCodeUsage edge %s", name) + return fmt.Errorf("unknown Proxy edge %s", name) } -// ProxyMutation represents an operation that mutates the Proxy nodes in the graph. -type ProxyMutation struct { +// RedeemCodeMutation represents an operation that mutates the RedeemCode nodes in the graph. +type RedeemCodeMutation struct { config - op Op - typ string - id *int64 - created_at *time.Time - updated_at *time.Time - deleted_at *time.Time - name *string - protocol *string - host *string - port *int - addport *int - username *string - password *string - status *string - clearedFields map[string]struct{} - accounts map[int64]struct{} - removedaccounts map[int64]struct{} - clearedaccounts bool - done bool - oldValue func(context.Context) (*Proxy, error) - predicates []predicate.Proxy + op Op + typ string + id *int64 + code *string + _type *string + value *float64 + addvalue *float64 + status *string + used_at *time.Time + notes *string + created_at *time.Time + validity_days *int + addvalidity_days *int + clearedFields map[string]struct{} + user *int64 + cleareduser bool + group *int64 + clearedgroup bool + done bool + oldValue func(context.Context) (*RedeemCode, error) + predicates []predicate.RedeemCode } -var _ ent.Mutation = (*ProxyMutation)(nil) +var _ ent.Mutation = (*RedeemCodeMutation)(nil) -// proxyOption allows management of the mutation configuration using functional options. -type proxyOption func(*ProxyMutation) +// redeemcodeOption allows management of the mutation configuration using functional options. +type redeemcodeOption func(*RedeemCodeMutation) -// newProxyMutation creates new mutation for the Proxy entity. -func newProxyMutation(c config, op Op, opts ...proxyOption) *ProxyMutation { - m := &ProxyMutation{ +// newRedeemCodeMutation creates new mutation for the RedeemCode entity. +func newRedeemCodeMutation(c config, op Op, opts ...redeemcodeOption) *RedeemCodeMutation { + m := &RedeemCodeMutation{ config: c, op: op, - typ: TypeProxy, + typ: TypeRedeemCode, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -13792,20 +19205,20 @@ func newProxyMutation(c config, op Op, opts ...proxyOption) *ProxyMutation { return m } -// withProxyID sets the ID field of the mutation. -func withProxyID(id int64) proxyOption { - return func(m *ProxyMutation) { +// withRedeemCodeID sets the ID field of the mutation. +func withRedeemCodeID(id int64) redeemcodeOption { + return func(m *RedeemCodeMutation) { var ( err error once sync.Once - value *Proxy + value *RedeemCode ) - m.oldValue = func(ctx context.Context) (*Proxy, error) { + m.oldValue = func(ctx context.Context) (*RedeemCode, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().Proxy.Get(ctx, id) + value, err = m.Client().RedeemCode.Get(ctx, id) } }) return value, err @@ -13814,10 +19227,10 @@ func withProxyID(id int64) proxyOption { } } -// withProxy sets the old Proxy of the mutation. -func withProxy(node *Proxy) proxyOption { - return func(m *ProxyMutation) { - m.oldValue = func(context.Context) (*Proxy, error) { +// withRedeemCode sets the old RedeemCode of the mutation. +func withRedeemCode(node *RedeemCode) redeemcodeOption { + return func(m *RedeemCodeMutation) { + m.oldValue = func(context.Context) (*RedeemCode, error) { return node, nil } m.id = &node.ID @@ -13826,7 +19239,7 @@ func withProxy(node *Proxy) proxyOption { // 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 ProxyMutation) Client() *Client { +func (m RedeemCodeMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -13834,7 +19247,7 @@ func (m ProxyMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m ProxyMutation) Tx() (*Tx, error) { +func (m RedeemCodeMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("ent: mutation is not running in a transaction") } @@ -13845,7 +19258,7 @@ func (m ProxyMutation) Tx() (*Tx, error) { // 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 *ProxyMutation) ID() (id int64, exists bool) { +func (m *RedeemCodeMutation) ID() (id int64, exists bool) { if m.id == nil { return } @@ -13856,7 +19269,7 @@ func (m *ProxyMutation) ID() (id int64, exists bool) { // 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 *ProxyMutation) IDs(ctx context.Context) ([]int64, error) { +func (m *RedeemCodeMutation) IDs(ctx context.Context) ([]int64, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -13865,494 +19278,540 @@ func (m *ProxyMutation) IDs(ctx context.Context) ([]int64, error) { } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().Proxy.Query().Where(m.predicates...).IDs(ctx) + return m.Client().RedeemCode.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } -// SetCreatedAt sets the "created_at" field. -func (m *ProxyMutation) SetCreatedAt(t time.Time) { - m.created_at = &t +// SetCode sets the "code" field. +func (m *RedeemCodeMutation) SetCode(s string) { + m.code = &s } -// CreatedAt returns the value of the "created_at" field in the mutation. -func (m *ProxyMutation) CreatedAt() (r time.Time, exists bool) { - v := m.created_at +// Code returns the value of the "code" field in the mutation. +func (m *RedeemCodeMutation) Code() (r string, exists bool) { + v := m.code if v == nil { return } return *v, true } -// OldCreatedAt returns the old "created_at" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldCode returns the old "code" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { +func (m *RedeemCodeMutation) OldCode(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + return v, errors.New("OldCode 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") + 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 OldCreatedAt: %w", err) + return v, fmt.Errorf("querying old value for OldCode: %w", err) } - return oldValue.CreatedAt, nil + return oldValue.Code, nil } -// ResetCreatedAt resets all changes to the "created_at" field. -func (m *ProxyMutation) ResetCreatedAt() { - m.created_at = nil +// ResetCode resets all changes to the "code" field. +func (m *RedeemCodeMutation) ResetCode() { + m.code = nil } -// SetUpdatedAt sets the "updated_at" field. -func (m *ProxyMutation) SetUpdatedAt(t time.Time) { - m.updated_at = &t +// SetType sets the "type" field. +func (m *RedeemCodeMutation) SetType(s string) { + m._type = &s } -// UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *ProxyMutation) UpdatedAt() (r time.Time, exists bool) { - v := m.updated_at +// GetType returns the value of the "type" field in the mutation. +func (m *RedeemCodeMutation) GetType() (r string, exists bool) { + v := m._type if v == nil { return } return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldType returns the old "type" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { +func (m *RedeemCodeMutation) OldType(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + return v, errors.New("OldType 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") + return v, errors.New("OldType 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 v, fmt.Errorf("querying old value for OldType: %w", err) } - return oldValue.UpdatedAt, nil + return oldValue.Type, nil } -// ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *ProxyMutation) ResetUpdatedAt() { - m.updated_at = nil +// ResetType resets all changes to the "type" field. +func (m *RedeemCodeMutation) ResetType() { + m._type = nil } -// SetDeletedAt sets the "deleted_at" field. -func (m *ProxyMutation) SetDeletedAt(t time.Time) { - m.deleted_at = &t +// SetValue sets the "value" field. +func (m *RedeemCodeMutation) SetValue(f float64) { + m.value = &f + m.addvalue = nil } -// DeletedAt returns the value of the "deleted_at" field in the mutation. -func (m *ProxyMutation) DeletedAt() (r time.Time, exists bool) { - v := m.deleted_at +// Value returns the value of the "value" field in the mutation. +func (m *RedeemCodeMutation) Value() (r float64, exists bool) { + v := m.value if v == nil { return } return *v, true } -// OldDeletedAt returns the old "deleted_at" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldValue returns the old "value" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) { +func (m *RedeemCodeMutation) OldValue(ctx context.Context) (v float64, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations") + return v, errors.New("OldValue is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldDeletedAt requires an ID field in the mutation") + return v, errors.New("OldValue requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err) + return v, fmt.Errorf("querying old value for OldValue: %w", err) } - return oldValue.DeletedAt, nil + return oldValue.Value, nil } -// ClearDeletedAt clears the value of the "deleted_at" field. -func (m *ProxyMutation) ClearDeletedAt() { - m.deleted_at = nil - m.clearedFields[proxy.FieldDeletedAt] = struct{}{} +// AddValue adds f to the "value" field. +func (m *RedeemCodeMutation) AddValue(f float64) { + if m.addvalue != nil { + *m.addvalue += f + } else { + m.addvalue = &f + } } -// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation. -func (m *ProxyMutation) DeletedAtCleared() bool { - _, ok := m.clearedFields[proxy.FieldDeletedAt] - return ok +// AddedValue returns the value that was added to the "value" field in this mutation. +func (m *RedeemCodeMutation) AddedValue() (r float64, exists bool) { + v := m.addvalue + if v == nil { + return + } + return *v, true } -// ResetDeletedAt resets all changes to the "deleted_at" field. -func (m *ProxyMutation) ResetDeletedAt() { - m.deleted_at = nil - delete(m.clearedFields, proxy.FieldDeletedAt) +// ResetValue resets all changes to the "value" field. +func (m *RedeemCodeMutation) ResetValue() { + m.value = nil + m.addvalue = nil } -// SetName sets the "name" field. -func (m *ProxyMutation) SetName(s string) { - m.name = &s +// SetStatus sets the "status" field. +func (m *RedeemCodeMutation) SetStatus(s string) { + m.status = &s } -// Name returns the value of the "name" field in the mutation. -func (m *ProxyMutation) Name() (r string, exists bool) { - v := m.name +// Status returns the value of the "status" field in the mutation. +func (m *RedeemCodeMutation) Status() (r string, exists bool) { + v := m.status if v == nil { return } return *v, true } -// OldName returns the old "name" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldStatus returns the old "status" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldName(ctx context.Context) (v string, err error) { +func (m *RedeemCodeMutation) OldStatus(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldName is only allowed on UpdateOne operations") + return v, errors.New("OldStatus is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldName requires an ID field in the mutation") + 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 OldName: %w", err) + return v, fmt.Errorf("querying old value for OldStatus: %w", err) } - return oldValue.Name, nil + return oldValue.Status, nil } -// ResetName resets all changes to the "name" field. -func (m *ProxyMutation) ResetName() { - m.name = nil +// ResetStatus resets all changes to the "status" field. +func (m *RedeemCodeMutation) ResetStatus() { + m.status = nil } -// SetProtocol sets the "protocol" field. -func (m *ProxyMutation) SetProtocol(s string) { - m.protocol = &s +// SetUsedBy sets the "used_by" field. +func (m *RedeemCodeMutation) SetUsedBy(i int64) { + m.user = &i } -// Protocol returns the value of the "protocol" field in the mutation. -func (m *ProxyMutation) Protocol() (r string, exists bool) { - v := m.protocol +// UsedBy returns the value of the "used_by" field in the mutation. +func (m *RedeemCodeMutation) UsedBy() (r int64, exists bool) { + v := m.user if v == nil { return } return *v, true } -// OldProtocol returns the old "protocol" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldUsedBy returns the old "used_by" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldProtocol(ctx context.Context) (v string, err error) { +func (m *RedeemCodeMutation) OldUsedBy(ctx context.Context) (v *int64, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldProtocol is only allowed on UpdateOne operations") + return v, errors.New("OldUsedBy is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldProtocol requires an ID field in the mutation") + return v, errors.New("OldUsedBy requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldProtocol: %w", err) + return v, fmt.Errorf("querying old value for OldUsedBy: %w", err) } - return oldValue.Protocol, nil + return oldValue.UsedBy, nil } -// ResetProtocol resets all changes to the "protocol" field. -func (m *ProxyMutation) ResetProtocol() { - m.protocol = nil +// ClearUsedBy clears the value of the "used_by" field. +func (m *RedeemCodeMutation) ClearUsedBy() { + m.user = nil + m.clearedFields[redeemcode.FieldUsedBy] = struct{}{} } -// SetHost sets the "host" field. -func (m *ProxyMutation) SetHost(s string) { - m.host = &s +// UsedByCleared returns if the "used_by" field was cleared in this mutation. +func (m *RedeemCodeMutation) UsedByCleared() bool { + _, ok := m.clearedFields[redeemcode.FieldUsedBy] + return ok } -// Host returns the value of the "host" field in the mutation. -func (m *ProxyMutation) Host() (r string, exists bool) { - v := m.host +// ResetUsedBy resets all changes to the "used_by" field. +func (m *RedeemCodeMutation) ResetUsedBy() { + m.user = nil + delete(m.clearedFields, redeemcode.FieldUsedBy) +} + +// SetUsedAt sets the "used_at" field. +func (m *RedeemCodeMutation) SetUsedAt(t time.Time) { + m.used_at = &t +} + +// UsedAt returns the value of the "used_at" field in the mutation. +func (m *RedeemCodeMutation) UsedAt() (r time.Time, exists bool) { + v := m.used_at if v == nil { return } return *v, true } -// OldHost returns the old "host" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldUsedAt returns the old "used_at" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldHost(ctx context.Context) (v string, err error) { +func (m *RedeemCodeMutation) OldUsedAt(ctx context.Context) (v *time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldHost is only allowed on UpdateOne operations") + return v, errors.New("OldUsedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldHost requires an ID field in the mutation") + 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 OldHost: %w", err) + return v, fmt.Errorf("querying old value for OldUsedAt: %w", err) } - return oldValue.Host, nil + return oldValue.UsedAt, nil } -// ResetHost resets all changes to the "host" field. -func (m *ProxyMutation) ResetHost() { - m.host = nil +// ClearUsedAt clears the value of the "used_at" field. +func (m *RedeemCodeMutation) ClearUsedAt() { + m.used_at = nil + m.clearedFields[redeemcode.FieldUsedAt] = struct{}{} } -// SetPort sets the "port" field. -func (m *ProxyMutation) SetPort(i int) { - m.port = &i - m.addport = nil +// UsedAtCleared returns if the "used_at" field was cleared in this mutation. +func (m *RedeemCodeMutation) UsedAtCleared() bool { + _, ok := m.clearedFields[redeemcode.FieldUsedAt] + return ok } -// Port returns the value of the "port" field in the mutation. -func (m *ProxyMutation) Port() (r int, exists bool) { - v := m.port +// ResetUsedAt resets all changes to the "used_at" field. +func (m *RedeemCodeMutation) ResetUsedAt() { + m.used_at = nil + delete(m.clearedFields, redeemcode.FieldUsedAt) +} + +// SetNotes sets the "notes" field. +func (m *RedeemCodeMutation) SetNotes(s string) { + m.notes = &s +} + +// Notes returns the value of the "notes" field in the mutation. +func (m *RedeemCodeMutation) Notes() (r string, exists bool) { + v := m.notes if v == nil { return } return *v, true } -// OldPort returns the old "port" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldNotes returns the old "notes" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldPort(ctx context.Context) (v int, err error) { +func (m *RedeemCodeMutation) OldNotes(ctx context.Context) (v *string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldPort is only allowed on UpdateOne operations") + return v, errors.New("OldNotes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldPort requires an ID field in the mutation") + 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 OldPort: %w", err) + return v, fmt.Errorf("querying old value for OldNotes: %w", err) } - return oldValue.Port, nil + return oldValue.Notes, nil } -// AddPort adds i to the "port" field. -func (m *ProxyMutation) AddPort(i int) { - if m.addport != nil { - *m.addport += i - } else { - m.addport = &i - } +// ClearNotes clears the value of the "notes" field. +func (m *RedeemCodeMutation) ClearNotes() { + m.notes = nil + m.clearedFields[redeemcode.FieldNotes] = struct{}{} } -// AddedPort returns the value that was added to the "port" field in this mutation. -func (m *ProxyMutation) AddedPort() (r int, exists bool) { - v := m.addport - if v == nil { - return - } - return *v, true +// NotesCleared returns if the "notes" field was cleared in this mutation. +func (m *RedeemCodeMutation) NotesCleared() bool { + _, ok := m.clearedFields[redeemcode.FieldNotes] + return ok } -// ResetPort resets all changes to the "port" field. -func (m *ProxyMutation) ResetPort() { - m.port = nil - m.addport = nil +// ResetNotes resets all changes to the "notes" field. +func (m *RedeemCodeMutation) ResetNotes() { + m.notes = nil + delete(m.clearedFields, redeemcode.FieldNotes) } -// SetUsername sets the "username" field. -func (m *ProxyMutation) SetUsername(s string) { - m.username = &s +// SetCreatedAt sets the "created_at" field. +func (m *RedeemCodeMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } -// Username returns the value of the "username" field in the mutation. -func (m *ProxyMutation) Username() (r string, exists bool) { - v := m.username +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *RedeemCodeMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at if v == nil { return } return *v, true } -// OldUsername returns the old "username" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldUsername(ctx context.Context) (v *string, err error) { +func (m *RedeemCodeMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsername is only allowed on UpdateOne operations") + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldUsername requires an ID field in the mutation") + 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 OldUsername: %w", err) + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } - return oldValue.Username, nil -} - -// ClearUsername clears the value of the "username" field. -func (m *ProxyMutation) ClearUsername() { - m.username = nil - m.clearedFields[proxy.FieldUsername] = struct{}{} -} - -// UsernameCleared returns if the "username" field was cleared in this mutation. -func (m *ProxyMutation) UsernameCleared() bool { - _, ok := m.clearedFields[proxy.FieldUsername] - return ok + return oldValue.CreatedAt, nil } -// ResetUsername resets all changes to the "username" field. -func (m *ProxyMutation) ResetUsername() { - m.username = nil - delete(m.clearedFields, proxy.FieldUsername) +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *RedeemCodeMutation) ResetCreatedAt() { + m.created_at = nil } -// SetPassword sets the "password" field. -func (m *ProxyMutation) SetPassword(s string) { - m.password = &s +// SetGroupID sets the "group_id" field. +func (m *RedeemCodeMutation) SetGroupID(i int64) { + m.group = &i } -// Password returns the value of the "password" field in the mutation. -func (m *ProxyMutation) Password() (r string, exists bool) { - v := m.password +// GroupID returns the value of the "group_id" field in the mutation. +func (m *RedeemCodeMutation) GroupID() (r int64, exists bool) { + v := m.group if v == nil { return } return *v, true } -// OldPassword returns the old "password" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldGroupID returns the old "group_id" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldPassword(ctx context.Context) (v *string, err error) { +func (m *RedeemCodeMutation) OldGroupID(ctx context.Context) (v *int64, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldPassword is only allowed on UpdateOne operations") + return v, errors.New("OldGroupID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldPassword requires an ID field in the mutation") + return v, errors.New("OldGroupID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldPassword: %w", err) + return v, fmt.Errorf("querying old value for OldGroupID: %w", err) } - return oldValue.Password, nil + return oldValue.GroupID, nil } -// ClearPassword clears the value of the "password" field. -func (m *ProxyMutation) ClearPassword() { - m.password = nil - m.clearedFields[proxy.FieldPassword] = struct{}{} +// ClearGroupID clears the value of the "group_id" field. +func (m *RedeemCodeMutation) ClearGroupID() { + m.group = nil + m.clearedFields[redeemcode.FieldGroupID] = struct{}{} } -// PasswordCleared returns if the "password" field was cleared in this mutation. -func (m *ProxyMutation) PasswordCleared() bool { - _, ok := m.clearedFields[proxy.FieldPassword] +// GroupIDCleared returns if the "group_id" field was cleared in this mutation. +func (m *RedeemCodeMutation) GroupIDCleared() bool { + _, ok := m.clearedFields[redeemcode.FieldGroupID] return ok } -// ResetPassword resets all changes to the "password" field. -func (m *ProxyMutation) ResetPassword() { - m.password = nil - delete(m.clearedFields, proxy.FieldPassword) +// ResetGroupID resets all changes to the "group_id" field. +func (m *RedeemCodeMutation) ResetGroupID() { + m.group = nil + delete(m.clearedFields, redeemcode.FieldGroupID) } -// SetStatus sets the "status" field. -func (m *ProxyMutation) SetStatus(s string) { - m.status = &s +// SetValidityDays sets the "validity_days" field. +func (m *RedeemCodeMutation) SetValidityDays(i int) { + m.validity_days = &i + m.addvalidity_days = nil } -// Status returns the value of the "status" field in the mutation. -func (m *ProxyMutation) Status() (r string, exists bool) { - v := m.status +// ValidityDays returns the value of the "validity_days" field in the mutation. +func (m *RedeemCodeMutation) ValidityDays() (r int, exists bool) { + v := m.validity_days if v == nil { return } return *v, true } -// OldStatus returns the old "status" field's value of the Proxy entity. -// If the Proxy object wasn't provided to the builder, the object is fetched from the database. +// OldValidityDays returns the old "validity_days" field's value of the RedeemCode entity. +// If the RedeemCode 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 *ProxyMutation) OldStatus(ctx context.Context) (v string, err error) { +func (m *RedeemCodeMutation) OldValidityDays(ctx context.Context) (v int, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldStatus is only allowed on UpdateOne operations") + return v, errors.New("OldValidityDays 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") + return v, errors.New("OldValidityDays 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 v, fmt.Errorf("querying old value for OldValidityDays: %w", err) } - return oldValue.Status, nil + return oldValue.ValidityDays, nil } -// ResetStatus resets all changes to the "status" field. -func (m *ProxyMutation) ResetStatus() { - m.status = nil +// AddValidityDays adds i to the "validity_days" field. +func (m *RedeemCodeMutation) AddValidityDays(i int) { + if m.addvalidity_days != nil { + *m.addvalidity_days += i + } else { + m.addvalidity_days = &i + } } -// AddAccountIDs adds the "accounts" edge to the Account entity by ids. -func (m *ProxyMutation) AddAccountIDs(ids ...int64) { - if m.accounts == nil { - m.accounts = make(map[int64]struct{}) - } - for i := range ids { - m.accounts[ids[i]] = struct{}{} +// AddedValidityDays returns the value that was added to the "validity_days" field in this mutation. +func (m *RedeemCodeMutation) AddedValidityDays() (r int, exists bool) { + v := m.addvalidity_days + if v == nil { + return } + return *v, true } -// ClearAccounts clears the "accounts" edge to the Account entity. -func (m *ProxyMutation) ClearAccounts() { - m.clearedaccounts = true +// ResetValidityDays resets all changes to the "validity_days" field. +func (m *RedeemCodeMutation) ResetValidityDays() { + m.validity_days = nil + m.addvalidity_days = nil } -// AccountsCleared reports if the "accounts" edge to the Account entity was cleared. -func (m *ProxyMutation) AccountsCleared() bool { - return m.clearedaccounts +// SetUserID sets the "user" edge to the User entity by id. +func (m *RedeemCodeMutation) SetUserID(id int64) { + m.user = &id } -// RemoveAccountIDs removes the "accounts" edge to the Account entity by IDs. -func (m *ProxyMutation) RemoveAccountIDs(ids ...int64) { - if m.removedaccounts == nil { - m.removedaccounts = make(map[int64]struct{}) - } - for i := range ids { - delete(m.accounts, ids[i]) - m.removedaccounts[ids[i]] = struct{}{} +// ClearUser clears the "user" edge to the User entity. +func (m *RedeemCodeMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[redeemcode.FieldUsedBy] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *RedeemCodeMutation) UserCleared() bool { + return m.UsedByCleared() || m.cleareduser +} + +// UserID returns the "user" edge ID in the mutation. +func (m *RedeemCodeMutation) UserID() (id int64, exists bool) { + if m.user != nil { + return *m.user, true } + return } -// RemovedAccounts returns the removed IDs of the "accounts" edge to the Account entity. -func (m *ProxyMutation) RemovedAccountsIDs() (ids []int64) { - for id := range m.removedaccounts { - ids = append(ids, id) +// 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 *RedeemCodeMutation) UserIDs() (ids []int64) { + if id := m.user; id != nil { + ids = append(ids, *id) } return } -// AccountsIDs returns the "accounts" edge IDs in the mutation. -func (m *ProxyMutation) AccountsIDs() (ids []int64) { - for id := range m.accounts { - ids = append(ids, id) +// ResetUser resets all changes to the "user" edge. +func (m *RedeemCodeMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// ClearGroup clears the "group" edge to the Group entity. +func (m *RedeemCodeMutation) ClearGroup() { + m.clearedgroup = true + m.clearedFields[redeemcode.FieldGroupID] = struct{}{} +} + +// GroupCleared reports if the "group" edge to the Group entity was cleared. +func (m *RedeemCodeMutation) GroupCleared() bool { + return m.GroupIDCleared() || m.clearedgroup +} + +// GroupIDs returns the "group" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// GroupID instead. It exists only for internal usage by the builders. +func (m *RedeemCodeMutation) GroupIDs() (ids []int64) { + if id := m.group; id != nil { + ids = append(ids, *id) } return } -// ResetAccounts resets all changes to the "accounts" edge. -func (m *ProxyMutation) ResetAccounts() { - m.accounts = nil - m.clearedaccounts = false - m.removedaccounts = nil +// ResetGroup resets all changes to the "group" edge. +func (m *RedeemCodeMutation) ResetGroup() { + m.group = nil + m.clearedgroup = false } -// Where appends a list predicates to the ProxyMutation builder. -func (m *ProxyMutation) Where(ps ...predicate.Proxy) { +// Where appends a list predicates to the RedeemCodeMutation builder. +func (m *RedeemCodeMutation) Where(ps ...predicate.RedeemCode) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the ProxyMutation builder. Using this method, +// WhereP appends storage-level predicates to the RedeemCodeMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *ProxyMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.Proxy, len(ps)) +func (m *RedeemCodeMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.RedeemCode, len(ps)) for i := range ps { p[i] = ps[i] } @@ -14360,54 +19819,54 @@ func (m *ProxyMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *ProxyMutation) Op() Op { +func (m *RedeemCodeMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *ProxyMutation) SetOp(op Op) { +func (m *RedeemCodeMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (Proxy). -func (m *ProxyMutation) Type() string { +// Type returns the node type of this mutation (RedeemCode). +func (m *RedeemCodeMutation) 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 *ProxyMutation) Fields() []string { +func (m *RedeemCodeMutation) Fields() []string { fields := make([]string, 0, 10) - if m.created_at != nil { - fields = append(fields, proxy.FieldCreatedAt) + if m.code != nil { + fields = append(fields, redeemcode.FieldCode) } - if m.updated_at != nil { - fields = append(fields, proxy.FieldUpdatedAt) + if m._type != nil { + fields = append(fields, redeemcode.FieldType) } - if m.deleted_at != nil { - fields = append(fields, proxy.FieldDeletedAt) + if m.value != nil { + fields = append(fields, redeemcode.FieldValue) } - if m.name != nil { - fields = append(fields, proxy.FieldName) + if m.status != nil { + fields = append(fields, redeemcode.FieldStatus) } - if m.protocol != nil { - fields = append(fields, proxy.FieldProtocol) + if m.user != nil { + fields = append(fields, redeemcode.FieldUsedBy) } - if m.host != nil { - fields = append(fields, proxy.FieldHost) + if m.used_at != nil { + fields = append(fields, redeemcode.FieldUsedAt) } - if m.port != nil { - fields = append(fields, proxy.FieldPort) + if m.notes != nil { + fields = append(fields, redeemcode.FieldNotes) } - if m.username != nil { - fields = append(fields, proxy.FieldUsername) + if m.created_at != nil { + fields = append(fields, redeemcode.FieldCreatedAt) } - if m.password != nil { - fields = append(fields, proxy.FieldPassword) + if m.group != nil { + fields = append(fields, redeemcode.FieldGroupID) } - if m.status != nil { - fields = append(fields, proxy.FieldStatus) + if m.validity_days != nil { + fields = append(fields, redeemcode.FieldValidityDays) } return fields } @@ -14415,28 +19874,28 @@ func (m *ProxyMutation) Fields() []string { // 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 *ProxyMutation) Field(name string) (ent.Value, bool) { +func (m *RedeemCodeMutation) Field(name string) (ent.Value, bool) { switch name { - case proxy.FieldCreatedAt: - return m.CreatedAt() - case proxy.FieldUpdatedAt: - return m.UpdatedAt() - case proxy.FieldDeletedAt: - return m.DeletedAt() - case proxy.FieldName: - return m.Name() - case proxy.FieldProtocol: - return m.Protocol() - case proxy.FieldHost: - return m.Host() - case proxy.FieldPort: - return m.Port() - case proxy.FieldUsername: - return m.Username() - case proxy.FieldPassword: - return m.Password() - case proxy.FieldStatus: + case redeemcode.FieldCode: + return m.Code() + case redeemcode.FieldType: + return m.GetType() + case redeemcode.FieldValue: + return m.Value() + case redeemcode.FieldStatus: return m.Status() + case redeemcode.FieldUsedBy: + return m.UsedBy() + case redeemcode.FieldUsedAt: + return m.UsedAt() + case redeemcode.FieldNotes: + return m.Notes() + case redeemcode.FieldCreatedAt: + return m.CreatedAt() + case redeemcode.FieldGroupID: + return m.GroupID() + case redeemcode.FieldValidityDays: + return m.ValidityDays() } return nil, false } @@ -14444,117 +19903,120 @@ func (m *ProxyMutation) Field(name string) (ent.Value, bool) { // 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 *ProxyMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *RedeemCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case proxy.FieldCreatedAt: - return m.OldCreatedAt(ctx) - case proxy.FieldUpdatedAt: - return m.OldUpdatedAt(ctx) - case proxy.FieldDeletedAt: - return m.OldDeletedAt(ctx) - case proxy.FieldName: - return m.OldName(ctx) - case proxy.FieldProtocol: - return m.OldProtocol(ctx) - case proxy.FieldHost: - return m.OldHost(ctx) - case proxy.FieldPort: - return m.OldPort(ctx) - case proxy.FieldUsername: - return m.OldUsername(ctx) - case proxy.FieldPassword: - return m.OldPassword(ctx) - case proxy.FieldStatus: + case redeemcode.FieldCode: + return m.OldCode(ctx) + case redeemcode.FieldType: + return m.OldType(ctx) + case redeemcode.FieldValue: + return m.OldValue(ctx) + case redeemcode.FieldStatus: return m.OldStatus(ctx) + case redeemcode.FieldUsedBy: + return m.OldUsedBy(ctx) + case redeemcode.FieldUsedAt: + return m.OldUsedAt(ctx) + case redeemcode.FieldNotes: + return m.OldNotes(ctx) + case redeemcode.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case redeemcode.FieldGroupID: + return m.OldGroupID(ctx) + case redeemcode.FieldValidityDays: + return m.OldValidityDays(ctx) } - return nil, fmt.Errorf("unknown Proxy field %s", name) + return nil, fmt.Errorf("unknown RedeemCode 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 *ProxyMutation) SetField(name string, value ent.Value) error { +func (m *RedeemCodeMutation) SetField(name string, value ent.Value) error { switch name { - case proxy.FieldCreatedAt: - v, ok := value.(time.Time) + case redeemcode.FieldCode: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetCreatedAt(v) + m.SetCode(v) return nil - case proxy.FieldUpdatedAt: - v, ok := value.(time.Time) + case redeemcode.FieldType: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetUpdatedAt(v) + m.SetType(v) return nil - case proxy.FieldDeletedAt: - v, ok := value.(time.Time) + case redeemcode.FieldValue: + v, ok := value.(float64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetDeletedAt(v) + m.SetValue(v) return nil - case proxy.FieldName: + case redeemcode.FieldStatus: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetName(v) + m.SetStatus(v) return nil - case proxy.FieldProtocol: - v, ok := value.(string) + case redeemcode.FieldUsedBy: + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetProtocol(v) + m.SetUsedBy(v) return nil - case proxy.FieldHost: - v, ok := value.(string) + case redeemcode.FieldUsedAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetHost(v) + m.SetUsedAt(v) return nil - case proxy.FieldPort: - v, ok := value.(int) + case redeemcode.FieldNotes: + v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetPort(v) + m.SetNotes(v) return nil - case proxy.FieldUsername: - v, ok := value.(string) + case redeemcode.FieldCreatedAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetUsername(v) + m.SetCreatedAt(v) return nil - case proxy.FieldPassword: - v, ok := value.(string) + case redeemcode.FieldGroupID: + v, ok := value.(int64) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetPassword(v) + m.SetGroupID(v) return nil - case proxy.FieldStatus: - v, ok := value.(string) + case redeemcode.FieldValidityDays: + v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetStatus(v) + m.SetValidityDays(v) return nil } - return fmt.Errorf("unknown Proxy field %s", name) + return fmt.Errorf("unknown RedeemCode field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *ProxyMutation) AddedFields() []string { +func (m *RedeemCodeMutation) AddedFields() []string { var fields []string - if m.addport != nil { - fields = append(fields, proxy.FieldPort) + if m.addvalue != nil { + fields = append(fields, redeemcode.FieldValue) + } + if m.addvalidity_days != nil { + fields = append(fields, redeemcode.FieldValidityDays) } return fields } @@ -14562,10 +20024,12 @@ func (m *ProxyMutation) AddedFields() []string { // 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 *ProxyMutation) AddedField(name string) (ent.Value, bool) { +func (m *RedeemCodeMutation) AddedField(name string) (ent.Value, bool) { switch name { - case proxy.FieldPort: - return m.AddedPort() + case redeemcode.FieldValue: + return m.AddedValue() + case redeemcode.FieldValidityDays: + return m.AddedValidityDays() } return nil, false } @@ -14573,218 +20037,229 @@ func (m *ProxyMutation) AddedField(name string) (ent.Value, bool) { // 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 *ProxyMutation) AddField(name string, value ent.Value) error { +func (m *RedeemCodeMutation) AddField(name string, value ent.Value) error { switch name { - case proxy.FieldPort: + case redeemcode.FieldValue: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddValue(v) + return nil + case redeemcode.FieldValidityDays: v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.AddPort(v) + m.AddValidityDays(v) return nil } - return fmt.Errorf("unknown Proxy numeric field %s", name) + return fmt.Errorf("unknown RedeemCode numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *ProxyMutation) ClearedFields() []string { +func (m *RedeemCodeMutation) ClearedFields() []string { var fields []string - if m.FieldCleared(proxy.FieldDeletedAt) { - fields = append(fields, proxy.FieldDeletedAt) + if m.FieldCleared(redeemcode.FieldUsedBy) { + fields = append(fields, redeemcode.FieldUsedBy) } - if m.FieldCleared(proxy.FieldUsername) { - fields = append(fields, proxy.FieldUsername) + if m.FieldCleared(redeemcode.FieldUsedAt) { + fields = append(fields, redeemcode.FieldUsedAt) } - if m.FieldCleared(proxy.FieldPassword) { - fields = append(fields, proxy.FieldPassword) + if m.FieldCleared(redeemcode.FieldNotes) { + fields = append(fields, redeemcode.FieldNotes) + } + if m.FieldCleared(redeemcode.FieldGroupID) { + fields = append(fields, redeemcode.FieldGroupID) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *ProxyMutation) FieldCleared(name string) bool { +func (m *RedeemCodeMutation) 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 *ProxyMutation) ClearField(name string) error { +func (m *RedeemCodeMutation) ClearField(name string) error { switch name { - case proxy.FieldDeletedAt: - m.ClearDeletedAt() + case redeemcode.FieldUsedBy: + m.ClearUsedBy() return nil - case proxy.FieldUsername: - m.ClearUsername() + case redeemcode.FieldUsedAt: + m.ClearUsedAt() return nil - case proxy.FieldPassword: - m.ClearPassword() + case redeemcode.FieldNotes: + m.ClearNotes() + return nil + case redeemcode.FieldGroupID: + m.ClearGroupID() return nil } - return fmt.Errorf("unknown Proxy nullable field %s", name) + return fmt.Errorf("unknown RedeemCode 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 *ProxyMutation) ResetField(name string) error { +func (m *RedeemCodeMutation) ResetField(name string) error { switch name { - case proxy.FieldCreatedAt: - m.ResetCreatedAt() + case redeemcode.FieldCode: + m.ResetCode() return nil - case proxy.FieldUpdatedAt: - m.ResetUpdatedAt() + case redeemcode.FieldType: + m.ResetType() return nil - case proxy.FieldDeletedAt: - m.ResetDeletedAt() + case redeemcode.FieldValue: + m.ResetValue() return nil - case proxy.FieldName: - m.ResetName() + case redeemcode.FieldStatus: + m.ResetStatus() return nil - case proxy.FieldProtocol: - m.ResetProtocol() + case redeemcode.FieldUsedBy: + m.ResetUsedBy() return nil - case proxy.FieldHost: - m.ResetHost() + case redeemcode.FieldUsedAt: + m.ResetUsedAt() return nil - case proxy.FieldPort: - m.ResetPort() + case redeemcode.FieldNotes: + m.ResetNotes() return nil - case proxy.FieldUsername: - m.ResetUsername() + case redeemcode.FieldCreatedAt: + m.ResetCreatedAt() return nil - case proxy.FieldPassword: - m.ResetPassword() + case redeemcode.FieldGroupID: + m.ResetGroupID() return nil - case proxy.FieldStatus: - m.ResetStatus() + case redeemcode.FieldValidityDays: + m.ResetValidityDays() return nil } - return fmt.Errorf("unknown Proxy field %s", name) + return fmt.Errorf("unknown RedeemCode field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *ProxyMutation) AddedEdges() []string { - edges := make([]string, 0, 1) - if m.accounts != nil { - edges = append(edges, proxy.EdgeAccounts) +func (m *RedeemCodeMutation) AddedEdges() []string { + edges := make([]string, 0, 2) + if m.user != nil { + edges = append(edges, redeemcode.EdgeUser) + } + if m.group != nil { + edges = append(edges, redeemcode.EdgeGroup) } return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *ProxyMutation) AddedIDs(name string) []ent.Value { +func (m *RedeemCodeMutation) AddedIDs(name string) []ent.Value { switch name { - case proxy.EdgeAccounts: - ids := make([]ent.Value, 0, len(m.accounts)) - for id := range m.accounts { - ids = append(ids, id) + case redeemcode.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + case redeemcode.EdgeGroup: + if id := m.group; id != nil { + return []ent.Value{*id} } - return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *ProxyMutation) RemovedEdges() []string { - edges := make([]string, 0, 1) - if m.removedaccounts != nil { - edges = append(edges, proxy.EdgeAccounts) - } +func (m *RedeemCodeMutation) 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 *ProxyMutation) RemovedIDs(name string) []ent.Value { - switch name { - case proxy.EdgeAccounts: - ids := make([]ent.Value, 0, len(m.removedaccounts)) - for id := range m.removedaccounts { - ids = append(ids, id) - } - return ids - } +func (m *RedeemCodeMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *ProxyMutation) ClearedEdges() []string { - edges := make([]string, 0, 1) - if m.clearedaccounts { - edges = append(edges, proxy.EdgeAccounts) +func (m *RedeemCodeMutation) ClearedEdges() []string { + edges := make([]string, 0, 2) + if m.cleareduser { + edges = append(edges, redeemcode.EdgeUser) + } + if m.clearedgroup { + edges = append(edges, redeemcode.EdgeGroup) } return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *ProxyMutation) EdgeCleared(name string) bool { +func (m *RedeemCodeMutation) EdgeCleared(name string) bool { switch name { - case proxy.EdgeAccounts: - return m.clearedaccounts + case redeemcode.EdgeUser: + return m.cleareduser + case redeemcode.EdgeGroup: + return m.clearedgroup } 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 *ProxyMutation) ClearEdge(name string) error { +func (m *RedeemCodeMutation) ClearEdge(name string) error { switch name { + case redeemcode.EdgeUser: + m.ClearUser() + return nil + case redeemcode.EdgeGroup: + m.ClearGroup() + return nil } - return fmt.Errorf("unknown Proxy unique edge %s", name) + return fmt.Errorf("unknown RedeemCode 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 *ProxyMutation) ResetEdge(name string) error { +func (m *RedeemCodeMutation) ResetEdge(name string) error { switch name { - case proxy.EdgeAccounts: - m.ResetAccounts() + case redeemcode.EdgeUser: + m.ResetUser() + return nil + case redeemcode.EdgeGroup: + m.ResetGroup() return nil } - return fmt.Errorf("unknown Proxy edge %s", name) + return fmt.Errorf("unknown RedeemCode edge %s", name) } -// RedeemCodeMutation represents an operation that mutates the RedeemCode nodes in the graph. -type RedeemCodeMutation struct { +// SecuritySecretMutation represents an operation that mutates the SecuritySecret nodes in the graph. +type SecuritySecretMutation struct { config - op Op - typ string - id *int64 - code *string - _type *string - value *float64 - addvalue *float64 - status *string - used_at *time.Time - notes *string - created_at *time.Time - validity_days *int - addvalidity_days *int - clearedFields map[string]struct{} - user *int64 - cleareduser bool - group *int64 - clearedgroup bool - done bool - oldValue func(context.Context) (*RedeemCode, error) - predicates []predicate.RedeemCode + op Op + typ string + id *int64 + created_at *time.Time + updated_at *time.Time + key *string + value *string + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*SecuritySecret, error) + predicates []predicate.SecuritySecret } -var _ ent.Mutation = (*RedeemCodeMutation)(nil) +var _ ent.Mutation = (*SecuritySecretMutation)(nil) -// redeemcodeOption allows management of the mutation configuration using functional options. -type redeemcodeOption func(*RedeemCodeMutation) +// securitysecretOption allows management of the mutation configuration using functional options. +type securitysecretOption func(*SecuritySecretMutation) -// newRedeemCodeMutation creates new mutation for the RedeemCode entity. -func newRedeemCodeMutation(c config, op Op, opts ...redeemcodeOption) *RedeemCodeMutation { - m := &RedeemCodeMutation{ +// newSecuritySecretMutation creates new mutation for the SecuritySecret entity. +func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *SecuritySecretMutation { + m := &SecuritySecretMutation{ config: c, op: op, - typ: TypeRedeemCode, + typ: TypeSecuritySecret, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -14793,20 +20268,20 @@ func newRedeemCodeMutation(c config, op Op, opts ...redeemcodeOption) *RedeemCod return m } -// withRedeemCodeID sets the ID field of the mutation. -func withRedeemCodeID(id int64) redeemcodeOption { - return func(m *RedeemCodeMutation) { +// withSecuritySecretID sets the ID field of the mutation. +func withSecuritySecretID(id int64) securitysecretOption { + return func(m *SecuritySecretMutation) { var ( err error once sync.Once - value *RedeemCode + value *SecuritySecret ) - m.oldValue = func(ctx context.Context) (*RedeemCode, error) { + m.oldValue = func(ctx context.Context) (*SecuritySecret, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().RedeemCode.Get(ctx, id) + value, err = m.Client().SecuritySecret.Get(ctx, id) } }) return value, err @@ -14815,10 +20290,10 @@ func withRedeemCodeID(id int64) redeemcodeOption { } } -// withRedeemCode sets the old RedeemCode of the mutation. -func withRedeemCode(node *RedeemCode) redeemcodeOption { - return func(m *RedeemCodeMutation) { - m.oldValue = func(context.Context) (*RedeemCode, error) { +// withSecuritySecret sets the old SecuritySecret of the mutation. +func withSecuritySecret(node *SecuritySecret) securitysecretOption { + return func(m *SecuritySecretMutation) { + m.oldValue = func(context.Context) (*SecuritySecret, error) { return node, nil } m.id = &node.ID @@ -14827,7 +20302,7 @@ func withRedeemCode(node *RedeemCode) redeemcodeOption { // 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 RedeemCodeMutation) Client() *Client { +func (m SecuritySecretMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -14835,7 +20310,7 @@ func (m RedeemCodeMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m RedeemCodeMutation) Tx() (*Tx, error) { +func (m SecuritySecretMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("ent: mutation is not running in a transaction") } @@ -14846,7 +20321,7 @@ func (m RedeemCodeMutation) Tx() (*Tx, error) { // 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 *RedeemCodeMutation) ID() (id int64, exists bool) { +func (m *SecuritySecretMutation) ID() (id int64, exists bool) { if m.id == nil { return } @@ -14857,7 +20332,7 @@ func (m *RedeemCodeMutation) ID() (id int64, exists bool) { // 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 *RedeemCodeMutation) IDs(ctx context.Context) ([]int64, error) { +func (m *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -14866,540 +20341,616 @@ func (m *RedeemCodeMutation) IDs(ctx context.Context) ([]int64, error) { } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().RedeemCode.Query().Where(m.predicates...).IDs(ctx) + return m.Client().SecuritySecret.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 *RedeemCodeMutation) SetCode(s string) { - m.code = &s +// SetCreatedAt sets the "created_at" field. +func (m *SecuritySecretMutation) SetCreatedAt(t time.Time) { + m.created_at = &t } -// Code returns the value of the "code" field in the mutation. -func (m *RedeemCodeMutation) Code() (r string, exists bool) { - v := m.code +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *SecuritySecretMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at if v == nil { return } return *v, true } -// OldCode returns the old "code" field's value of the RedeemCode entity. -// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the SecuritySecret entity. +// If the SecuritySecret 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 *RedeemCodeMutation) OldCode(ctx context.Context) (v string, err error) { +func (m *SecuritySecretMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCode is only allowed on UpdateOne operations") + return v, errors.New("OldCreatedAt 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") + 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 OldCode: %w", err) + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } - return oldValue.Code, nil + return oldValue.CreatedAt, nil } -// ResetCode resets all changes to the "code" field. -func (m *RedeemCodeMutation) ResetCode() { - m.code = nil +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *SecuritySecretMutation) ResetCreatedAt() { + m.created_at = nil } -// SetType sets the "type" field. -func (m *RedeemCodeMutation) SetType(s string) { - m._type = &s +// SetUpdatedAt sets the "updated_at" field. +func (m *SecuritySecretMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } -// GetType returns the value of the "type" field in the mutation. -func (m *RedeemCodeMutation) GetType() (r string, exists bool) { - v := m._type +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *SecuritySecretMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at if v == nil { return } return *v, true } -// OldType returns the old "type" field's value of the RedeemCode entity. -// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database. +// OldUpdatedAt returns the old "updated_at" field's value of the SecuritySecret entity. +// If the SecuritySecret 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 *RedeemCodeMutation) OldType(ctx context.Context) (v string, err error) { +func (m *SecuritySecretMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldType is only allowed on UpdateOne operations") + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldType requires an ID field in the mutation") + 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 OldType: %w", err) + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) } - return oldValue.Type, nil + return oldValue.UpdatedAt, nil } -// ResetType resets all changes to the "type" field. -func (m *RedeemCodeMutation) ResetType() { - m._type = nil +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *SecuritySecretMutation) ResetUpdatedAt() { + m.updated_at = nil } -// SetValue sets the "value" field. -func (m *RedeemCodeMutation) SetValue(f float64) { - m.value = &f - m.addvalue = nil +// SetKey sets the "key" field. +func (m *SecuritySecretMutation) SetKey(s string) { + m.key = &s } -// Value returns the value of the "value" field in the mutation. -func (m *RedeemCodeMutation) Value() (r float64, exists bool) { - v := m.value +// Key returns the value of the "key" field in the mutation. +func (m *SecuritySecretMutation) Key() (r string, exists bool) { + v := m.key if v == nil { return } return *v, true } -// OldValue returns the old "value" field's value of the RedeemCode entity. -// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database. +// OldKey returns the old "key" field's value of the SecuritySecret entity. +// If the SecuritySecret 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 *RedeemCodeMutation) OldValue(ctx context.Context) (v float64, err error) { +func (m *SecuritySecretMutation) OldKey(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldValue is only allowed on UpdateOne operations") + return v, errors.New("OldKey is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldValue requires an ID field in the mutation") + return v, errors.New("OldKey requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldValue: %w", err) - } - return oldValue.Value, nil -} - -// AddValue adds f to the "value" field. -func (m *RedeemCodeMutation) AddValue(f float64) { - if m.addvalue != nil { - *m.addvalue += f - } else { - m.addvalue = &f - } -} - -// AddedValue returns the value that was added to the "value" field in this mutation. -func (m *RedeemCodeMutation) AddedValue() (r float64, exists bool) { - v := m.addvalue - if v == nil { - return + return v, fmt.Errorf("querying old value for OldKey: %w", err) } - return *v, true + return oldValue.Key, nil } -// ResetValue resets all changes to the "value" field. -func (m *RedeemCodeMutation) ResetValue() { - m.value = nil - m.addvalue = nil +// ResetKey resets all changes to the "key" field. +func (m *SecuritySecretMutation) ResetKey() { + m.key = nil } -// SetStatus sets the "status" field. -func (m *RedeemCodeMutation) SetStatus(s string) { - m.status = &s +// SetValue sets the "value" field. +func (m *SecuritySecretMutation) SetValue(s string) { + m.value = &s } -// Status returns the value of the "status" field in the mutation. -func (m *RedeemCodeMutation) Status() (r string, exists bool) { - v := m.status +// Value returns the value of the "value" field in the mutation. +func (m *SecuritySecretMutation) Value() (r string, exists bool) { + v := m.value if v == nil { return } return *v, true } -// OldStatus returns the old "status" field's value of the RedeemCode entity. -// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database. +// OldValue returns the old "value" field's value of the SecuritySecret entity. +// If the SecuritySecret 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 *RedeemCodeMutation) OldStatus(ctx context.Context) (v string, err error) { +func (m *SecuritySecretMutation) OldValue(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldStatus is only allowed on UpdateOne operations") + return v, errors.New("OldValue 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") + return v, errors.New("OldValue 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 v, fmt.Errorf("querying old value for OldValue: %w", err) + } + return oldValue.Value, nil +} + +// ResetValue resets all changes to the "value" field. +func (m *SecuritySecretMutation) ResetValue() { + m.value = nil +} + +// Where appends a list predicates to the SecuritySecretMutation builder. +func (m *SecuritySecretMutation) Where(ps ...predicate.SecuritySecret) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the SecuritySecretMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *SecuritySecretMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.SecuritySecret, len(ps)) + for i := range ps { + p[i] = ps[i] } - return oldValue.Status, nil + m.Where(p...) } -// ResetStatus resets all changes to the "status" field. -func (m *RedeemCodeMutation) ResetStatus() { - m.status = nil +// Op returns the operation name. +func (m *SecuritySecretMutation) Op() Op { + return m.op } -// SetUsedBy sets the "used_by" field. -func (m *RedeemCodeMutation) SetUsedBy(i int64) { - m.user = &i +// SetOp allows setting the mutation operation. +func (m *SecuritySecretMutation) SetOp(op Op) { + m.op = op } -// UsedBy returns the value of the "used_by" field in the mutation. -func (m *RedeemCodeMutation) UsedBy() (r int64, exists bool) { - v := m.user - if v == nil { - return - } - return *v, true +// Type returns the node type of this mutation (SecuritySecret). +func (m *SecuritySecretMutation) Type() string { + return m.typ } -// OldUsedBy returns the old "used_by" field's value of the RedeemCode entity. -// If the RedeemCode 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 *RedeemCodeMutation) OldUsedBy(ctx context.Context) (v *int64, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsedBy is only allowed on UpdateOne operations") +// 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 *SecuritySecretMutation) Fields() []string { + fields := make([]string, 0, 4) + if m.created_at != nil { + fields = append(fields, securitysecret.FieldCreatedAt) } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldUsedBy requires an ID field in the mutation") + if m.updated_at != nil { + fields = append(fields, securitysecret.FieldUpdatedAt) } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldUsedBy: %w", err) + if m.key != nil { + fields = append(fields, securitysecret.FieldKey) } - return oldValue.UsedBy, nil + if m.value != nil { + fields = append(fields, securitysecret.FieldValue) + } + return fields } -// ClearUsedBy clears the value of the "used_by" field. -func (m *RedeemCodeMutation) ClearUsedBy() { - m.user = nil - m.clearedFields[redeemcode.FieldUsedBy] = struct{}{} +// 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 *SecuritySecretMutation) Field(name string) (ent.Value, bool) { + switch name { + case securitysecret.FieldCreatedAt: + return m.CreatedAt() + case securitysecret.FieldUpdatedAt: + return m.UpdatedAt() + case securitysecret.FieldKey: + return m.Key() + case securitysecret.FieldValue: + return m.Value() + } + return nil, false } -// UsedByCleared returns if the "used_by" field was cleared in this mutation. -func (m *RedeemCodeMutation) UsedByCleared() bool { - _, ok := m.clearedFields[redeemcode.FieldUsedBy] - return ok +// 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 *SecuritySecretMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case securitysecret.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case securitysecret.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case securitysecret.FieldKey: + return m.OldKey(ctx) + case securitysecret.FieldValue: + return m.OldValue(ctx) + } + return nil, fmt.Errorf("unknown SecuritySecret field %s", name) } -// ResetUsedBy resets all changes to the "used_by" field. -func (m *RedeemCodeMutation) ResetUsedBy() { - m.user = nil - delete(m.clearedFields, redeemcode.FieldUsedBy) +// 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 *SecuritySecretMutation) SetField(name string, value ent.Value) error { + switch name { + case securitysecret.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 securitysecret.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case securitysecret.FieldKey: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetKey(v) + return nil + case securitysecret.FieldValue: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetValue(v) + return nil + } + return fmt.Errorf("unknown SecuritySecret field %s", name) } -// SetUsedAt sets the "used_at" field. -func (m *RedeemCodeMutation) SetUsedAt(t time.Time) { - m.used_at = &t +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *SecuritySecretMutation) AddedFields() []string { + return nil } -// UsedAt returns the value of the "used_at" field in the mutation. -func (m *RedeemCodeMutation) UsedAt() (r time.Time, exists bool) { - v := m.used_at - if v == nil { - return - } - return *v, true +// 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 *SecuritySecretMutation) AddedField(name string) (ent.Value, bool) { + return nil, false } -// OldUsedAt returns the old "used_at" field's value of the RedeemCode entity. -// If the RedeemCode 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 *RedeemCodeMutation) 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) +// 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 *SecuritySecretMutation) AddField(name string, value ent.Value) error { + switch name { } - return oldValue.UsedAt, nil + return fmt.Errorf("unknown SecuritySecret numeric field %s", name) } -// ClearUsedAt clears the value of the "used_at" field. -func (m *RedeemCodeMutation) ClearUsedAt() { - m.used_at = nil - m.clearedFields[redeemcode.FieldUsedAt] = struct{}{} +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *SecuritySecretMutation) ClearedFields() []string { + return nil } -// UsedAtCleared returns if the "used_at" field was cleared in this mutation. -func (m *RedeemCodeMutation) UsedAtCleared() bool { - _, ok := m.clearedFields[redeemcode.FieldUsedAt] +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *SecuritySecretMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] return ok } -// ResetUsedAt resets all changes to the "used_at" field. -func (m *RedeemCodeMutation) ResetUsedAt() { - m.used_at = nil - delete(m.clearedFields, redeemcode.FieldUsedAt) +// 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 *SecuritySecretMutation) ClearField(name string) error { + return fmt.Errorf("unknown SecuritySecret nullable field %s", name) } -// SetNotes sets the "notes" field. -func (m *RedeemCodeMutation) SetNotes(s string) { - m.notes = &s +// 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 *SecuritySecretMutation) ResetField(name string) error { + switch name { + case securitysecret.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case securitysecret.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case securitysecret.FieldKey: + m.ResetKey() + return nil + case securitysecret.FieldValue: + m.ResetValue() + return nil + } + return fmt.Errorf("unknown SecuritySecret field %s", name) } -// Notes returns the value of the "notes" field in the mutation. -func (m *RedeemCodeMutation) Notes() (r string, exists bool) { - v := m.notes - if v == nil { - return - } - return *v, true +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *SecuritySecretMutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges } -// OldNotes returns the old "notes" field's value of the RedeemCode entity. -// If the RedeemCode 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 *RedeemCodeMutation) 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 +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *SecuritySecretMutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *SecuritySecretMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *SecuritySecretMutation) RemovedIDs(name string) []ent.Value { + return nil } -// ClearNotes clears the value of the "notes" field. -func (m *RedeemCodeMutation) ClearNotes() { - m.notes = nil - m.clearedFields[redeemcode.FieldNotes] = struct{}{} +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *SecuritySecretMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges } -// NotesCleared returns if the "notes" field was cleared in this mutation. -func (m *RedeemCodeMutation) NotesCleared() bool { - _, ok := m.clearedFields[redeemcode.FieldNotes] - return ok +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *SecuritySecretMutation) EdgeCleared(name string) bool { + return false } -// ResetNotes resets all changes to the "notes" field. -func (m *RedeemCodeMutation) ResetNotes() { - m.notes = nil - delete(m.clearedFields, redeemcode.FieldNotes) +// 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 *SecuritySecretMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown SecuritySecret unique edge %s", name) } -// SetCreatedAt sets the "created_at" field. -func (m *RedeemCodeMutation) SetCreatedAt(t time.Time) { - m.created_at = &t +// 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 *SecuritySecretMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown SecuritySecret edge %s", name) } -// CreatedAt returns the value of the "created_at" field in the mutation. -func (m *RedeemCodeMutation) CreatedAt() (r time.Time, exists bool) { - v := m.created_at - if v == nil { - return - } - return *v, true +// SettingMutation represents an operation that mutates the Setting nodes in the graph. +type SettingMutation struct { + config + op Op + typ string + id *int64 + key *string + value *string + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*Setting, error) + predicates []predicate.Setting } -// OldCreatedAt returns the old "created_at" field's value of the RedeemCode entity. -// If the RedeemCode 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 *RedeemCodeMutation) 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") +var _ ent.Mutation = (*SettingMutation)(nil) + +// settingOption allows management of the mutation configuration using functional options. +type settingOption func(*SettingMutation) + +// newSettingMutation creates new mutation for the Setting entity. +func newSettingMutation(c config, op Op, opts ...settingOption) *SettingMutation { + m := &SettingMutation{ + config: c, + op: op, + typ: TypeSetting, + clearedFields: make(map[string]struct{}), } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + for _, opt := range opts { + opt(m) } - return oldValue.CreatedAt, nil -} - -// ResetCreatedAt resets all changes to the "created_at" field. -func (m *RedeemCodeMutation) ResetCreatedAt() { - m.created_at = nil + return m } -// SetGroupID sets the "group_id" field. -func (m *RedeemCodeMutation) SetGroupID(i int64) { - m.group = &i +// withSettingID sets the ID field of the mutation. +func withSettingID(id int64) settingOption { + return func(m *SettingMutation) { + var ( + err error + once sync.Once + value *Setting + ) + m.oldValue = func(ctx context.Context) (*Setting, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Setting.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } } -// GroupID returns the value of the "group_id" field in the mutation. -func (m *RedeemCodeMutation) GroupID() (r int64, exists bool) { - v := m.group - if v == nil { - return +// withSetting sets the old Setting of the mutation. +func withSetting(node *Setting) settingOption { + return func(m *SettingMutation) { + m.oldValue = func(context.Context) (*Setting, error) { + return node, nil + } + m.id = &node.ID } - return *v, true } -// OldGroupID returns the old "group_id" field's value of the RedeemCode entity. -// If the RedeemCode 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 *RedeemCodeMutation) OldGroupID(ctx context.Context) (v *int64, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldGroupID is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldGroupID requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldGroupID: %w", err) - } - return oldValue.GroupID, nil +// 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 SettingMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client } -// ClearGroupID clears the value of the "group_id" field. -func (m *RedeemCodeMutation) ClearGroupID() { - m.group = nil - m.clearedFields[redeemcode.FieldGroupID] = struct{}{} +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m SettingMutation) 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 } -// GroupIDCleared returns if the "group_id" field was cleared in this mutation. -func (m *RedeemCodeMutation) GroupIDCleared() bool { - _, ok := m.clearedFields[redeemcode.FieldGroupID] - return ok +// 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 *SettingMutation) ID() (id int64, exists bool) { + if m.id == nil { + return + } + return *m.id, true } -// ResetGroupID resets all changes to the "group_id" field. -func (m *RedeemCodeMutation) ResetGroupID() { - m.group = nil - delete(m.clearedFields, redeemcode.FieldGroupID) +// 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 *SettingMutation) 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().Setting.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } } -// SetValidityDays sets the "validity_days" field. -func (m *RedeemCodeMutation) SetValidityDays(i int) { - m.validity_days = &i - m.addvalidity_days = nil +// SetKey sets the "key" field. +func (m *SettingMutation) SetKey(s string) { + m.key = &s } -// ValidityDays returns the value of the "validity_days" field in the mutation. -func (m *RedeemCodeMutation) ValidityDays() (r int, exists bool) { - v := m.validity_days +// Key returns the value of the "key" field in the mutation. +func (m *SettingMutation) Key() (r string, exists bool) { + v := m.key if v == nil { return } return *v, true } -// OldValidityDays returns the old "validity_days" field's value of the RedeemCode entity. -// If the RedeemCode object wasn't provided to the builder, the object is fetched from the database. +// OldKey returns the old "key" field's value of the Setting entity. +// If the Setting 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 *RedeemCodeMutation) OldValidityDays(ctx context.Context) (v int, err error) { +func (m *SettingMutation) OldKey(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldValidityDays is only allowed on UpdateOne operations") + return v, errors.New("OldKey is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldValidityDays requires an ID field in the mutation") + return v, errors.New("OldKey requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldValidityDays: %w", err) - } - return oldValue.ValidityDays, nil -} - -// AddValidityDays adds i to the "validity_days" field. -func (m *RedeemCodeMutation) AddValidityDays(i int) { - if m.addvalidity_days != nil { - *m.addvalidity_days += i - } else { - m.addvalidity_days = &i - } -} - -// AddedValidityDays returns the value that was added to the "validity_days" field in this mutation. -func (m *RedeemCodeMutation) AddedValidityDays() (r int, exists bool) { - v := m.addvalidity_days - if v == nil { - return + return v, fmt.Errorf("querying old value for OldKey: %w", err) } - return *v, true -} - -// ResetValidityDays resets all changes to the "validity_days" field. -func (m *RedeemCodeMutation) ResetValidityDays() { - m.validity_days = nil - m.addvalidity_days = nil -} - -// SetUserID sets the "user" edge to the User entity by id. -func (m *RedeemCodeMutation) SetUserID(id int64) { - m.user = &id -} - -// ClearUser clears the "user" edge to the User entity. -func (m *RedeemCodeMutation) ClearUser() { - m.cleareduser = true - m.clearedFields[redeemcode.FieldUsedBy] = struct{}{} + return oldValue.Key, nil } -// UserCleared reports if the "user" edge to the User entity was cleared. -func (m *RedeemCodeMutation) UserCleared() bool { - return m.UsedByCleared() || m.cleareduser +// ResetKey resets all changes to the "key" field. +func (m *SettingMutation) ResetKey() { + m.key = nil } -// UserID returns the "user" edge ID in the mutation. -func (m *RedeemCodeMutation) UserID() (id int64, exists bool) { - if m.user != nil { - return *m.user, true +// SetValue sets the "value" field. +func (m *SettingMutation) SetValue(s string) { + m.value = &s +} + +// Value returns the value of the "value" field in the mutation. +func (m *SettingMutation) Value() (r string, exists bool) { + v := m.value + if v == nil { + return } - return + return *v, true } -// 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 *RedeemCodeMutation) UserIDs() (ids []int64) { - if id := m.user; id != nil { - ids = append(ids, *id) +// OldValue returns the old "value" field's value of the Setting entity. +// If the Setting 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 *SettingMutation) OldValue(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldValue is only allowed on UpdateOne operations") } - return + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldValue requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldValue: %w", err) + } + return oldValue.Value, nil } -// ResetUser resets all changes to the "user" edge. -func (m *RedeemCodeMutation) ResetUser() { - m.user = nil - m.cleareduser = false +// ResetValue resets all changes to the "value" field. +func (m *SettingMutation) ResetValue() { + m.value = nil } -// ClearGroup clears the "group" edge to the Group entity. -func (m *RedeemCodeMutation) ClearGroup() { - m.clearedgroup = true - m.clearedFields[redeemcode.FieldGroupID] = struct{}{} +// SetUpdatedAt sets the "updated_at" field. +func (m *SettingMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t } -// GroupCleared reports if the "group" edge to the Group entity was cleared. -func (m *RedeemCodeMutation) GroupCleared() bool { - return m.GroupIDCleared() || m.clearedgroup +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *SettingMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true } -// GroupIDs returns the "group" edge IDs in the mutation. -// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use -// GroupID instead. It exists only for internal usage by the builders. -func (m *RedeemCodeMutation) GroupIDs() (ids []int64) { - if id := m.group; id != nil { - ids = append(ids, *id) +// OldUpdatedAt returns the old "updated_at" field's value of the Setting entity. +// If the Setting 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 *SettingMutation) 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") } - return + 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 } -// ResetGroup resets all changes to the "group" edge. -func (m *RedeemCodeMutation) ResetGroup() { - m.group = nil - m.clearedgroup = false +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *SettingMutation) ResetUpdatedAt() { + m.updated_at = nil } -// Where appends a list predicates to the RedeemCodeMutation builder. -func (m *RedeemCodeMutation) Where(ps ...predicate.RedeemCode) { +// Where appends a list predicates to the SettingMutation builder. +func (m *SettingMutation) Where(ps ...predicate.Setting) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the RedeemCodeMutation builder. Using this method, +// WhereP appends storage-level predicates to the SettingMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *RedeemCodeMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.RedeemCode, len(ps)) +func (m *SettingMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Setting, len(ps)) for i := range ps { p[i] = ps[i] } @@ -15407,54 +20958,33 @@ func (m *RedeemCodeMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *RedeemCodeMutation) Op() Op { +func (m *SettingMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *RedeemCodeMutation) SetOp(op Op) { +func (m *SettingMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (RedeemCode). -func (m *RedeemCodeMutation) Type() string { +// Type returns the node type of this mutation (Setting). +func (m *SettingMutation) 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 *RedeemCodeMutation) Fields() []string { - fields := make([]string, 0, 10) - if m.code != nil { - fields = append(fields, redeemcode.FieldCode) - } - if m._type != nil { - fields = append(fields, redeemcode.FieldType) +func (m *SettingMutation) Fields() []string { + fields := make([]string, 0, 3) + if m.key != nil { + fields = append(fields, setting.FieldKey) } if m.value != nil { - fields = append(fields, redeemcode.FieldValue) - } - if m.status != nil { - fields = append(fields, redeemcode.FieldStatus) - } - if m.user != nil { - fields = append(fields, redeemcode.FieldUsedBy) - } - if m.used_at != nil { - fields = append(fields, redeemcode.FieldUsedAt) - } - if m.notes != nil { - fields = append(fields, redeemcode.FieldNotes) - } - if m.created_at != nil { - fields = append(fields, redeemcode.FieldCreatedAt) - } - if m.group != nil { - fields = append(fields, redeemcode.FieldGroupID) + fields = append(fields, setting.FieldValue) } - if m.validity_days != nil { - fields = append(fields, redeemcode.FieldValidityDays) + if m.updated_at != nil { + fields = append(fields, setting.FieldUpdatedAt) } return fields } @@ -15462,28 +20992,14 @@ func (m *RedeemCodeMutation) Fields() []string { // 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 *RedeemCodeMutation) Field(name string) (ent.Value, bool) { +func (m *SettingMutation) Field(name string) (ent.Value, bool) { switch name { - case redeemcode.FieldCode: - return m.Code() - case redeemcode.FieldType: - return m.GetType() - case redeemcode.FieldValue: + case setting.FieldKey: + return m.Key() + case setting.FieldValue: return m.Value() - case redeemcode.FieldStatus: - return m.Status() - case redeemcode.FieldUsedBy: - return m.UsedBy() - case redeemcode.FieldUsedAt: - return m.UsedAt() - case redeemcode.FieldNotes: - return m.Notes() - case redeemcode.FieldCreatedAt: - return m.CreatedAt() - case redeemcode.FieldGroupID: - return m.GroupID() - case redeemcode.FieldValidityDays: - return m.ValidityDays() + case setting.FieldUpdatedAt: + return m.UpdatedAt() } return nil, false } @@ -15491,363 +21007,195 @@ func (m *RedeemCodeMutation) Field(name string) (ent.Value, bool) { // 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 *RedeemCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *SettingMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case redeemcode.FieldCode: - return m.OldCode(ctx) - case redeemcode.FieldType: - return m.OldType(ctx) - case redeemcode.FieldValue: + case setting.FieldKey: + return m.OldKey(ctx) + case setting.FieldValue: return m.OldValue(ctx) - case redeemcode.FieldStatus: - return m.OldStatus(ctx) - case redeemcode.FieldUsedBy: - return m.OldUsedBy(ctx) - case redeemcode.FieldUsedAt: - return m.OldUsedAt(ctx) - case redeemcode.FieldNotes: - return m.OldNotes(ctx) - case redeemcode.FieldCreatedAt: - return m.OldCreatedAt(ctx) - case redeemcode.FieldGroupID: - return m.OldGroupID(ctx) - case redeemcode.FieldValidityDays: - return m.OldValidityDays(ctx) + case setting.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) } - return nil, fmt.Errorf("unknown RedeemCode field %s", name) + return nil, fmt.Errorf("unknown Setting 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 *RedeemCodeMutation) SetField(name string, value ent.Value) error { +func (m *SettingMutation) SetField(name string, value ent.Value) error { switch name { - case redeemcode.FieldCode: + case setting.FieldKey: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetCode(v) + m.SetKey(v) return nil - case redeemcode.FieldType: + case setting.FieldValue: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetType(v) - return nil - case redeemcode.FieldValue: - v, ok := value.(float64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } m.SetValue(v) - return nil - case redeemcode.FieldStatus: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetStatus(v) - return nil - case redeemcode.FieldUsedBy: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetUsedBy(v) - return nil - case redeemcode.FieldUsedAt: - v, ok := value.(time.Time) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetUsedAt(v) - return nil - case redeemcode.FieldNotes: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetNotes(v) - return nil - case redeemcode.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 redeemcode.FieldGroupID: - v, ok := value.(int64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetGroupID(v) - return nil - case redeemcode.FieldValidityDays: - v, ok := value.(int) + return nil + case setting.FieldUpdatedAt: + v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetValidityDays(v) + m.SetUpdatedAt(v) return nil } - return fmt.Errorf("unknown RedeemCode field %s", name) + return fmt.Errorf("unknown Setting field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *RedeemCodeMutation) AddedFields() []string { - var fields []string - if m.addvalue != nil { - fields = append(fields, redeemcode.FieldValue) - } - if m.addvalidity_days != nil { - fields = append(fields, redeemcode.FieldValidityDays) - } - return fields +func (m *SettingMutation) AddedFields() []string { + return nil } // 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 *RedeemCodeMutation) AddedField(name string) (ent.Value, bool) { - switch name { - case redeemcode.FieldValue: - return m.AddedValue() - case redeemcode.FieldValidityDays: - return m.AddedValidityDays() - } +func (m *SettingMutation) AddedField(name string) (ent.Value, bool) { 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 *RedeemCodeMutation) AddField(name string, value ent.Value) error { +func (m *SettingMutation) AddField(name string, value ent.Value) error { switch name { - case redeemcode.FieldValue: - v, ok := value.(float64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddValue(v) - return nil - case redeemcode.FieldValidityDays: - v, ok := value.(int) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddValidityDays(v) - return nil } - return fmt.Errorf("unknown RedeemCode numeric field %s", name) + return fmt.Errorf("unknown Setting numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *RedeemCodeMutation) ClearedFields() []string { - var fields []string - if m.FieldCleared(redeemcode.FieldUsedBy) { - fields = append(fields, redeemcode.FieldUsedBy) - } - if m.FieldCleared(redeemcode.FieldUsedAt) { - fields = append(fields, redeemcode.FieldUsedAt) - } - if m.FieldCleared(redeemcode.FieldNotes) { - fields = append(fields, redeemcode.FieldNotes) - } - if m.FieldCleared(redeemcode.FieldGroupID) { - fields = append(fields, redeemcode.FieldGroupID) - } - return fields +func (m *SettingMutation) ClearedFields() []string { + return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *RedeemCodeMutation) FieldCleared(name string) bool { +func (m *SettingMutation) 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 *RedeemCodeMutation) ClearField(name string) error { - switch name { - case redeemcode.FieldUsedBy: - m.ClearUsedBy() - return nil - case redeemcode.FieldUsedAt: - m.ClearUsedAt() - return nil - case redeemcode.FieldNotes: - m.ClearNotes() - return nil - case redeemcode.FieldGroupID: - m.ClearGroupID() - return nil - } - return fmt.Errorf("unknown RedeemCode nullable field %s", name) +func (m *SettingMutation) ClearField(name string) error { + return fmt.Errorf("unknown Setting 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 *RedeemCodeMutation) ResetField(name string) error { +func (m *SettingMutation) ResetField(name string) error { switch name { - case redeemcode.FieldCode: - m.ResetCode() - return nil - case redeemcode.FieldType: - m.ResetType() + case setting.FieldKey: + m.ResetKey() return nil - case redeemcode.FieldValue: + case setting.FieldValue: m.ResetValue() return nil - case redeemcode.FieldStatus: - m.ResetStatus() - return nil - case redeemcode.FieldUsedBy: - m.ResetUsedBy() - return nil - case redeemcode.FieldUsedAt: - m.ResetUsedAt() - return nil - case redeemcode.FieldNotes: - m.ResetNotes() - return nil - case redeemcode.FieldCreatedAt: - m.ResetCreatedAt() - return nil - case redeemcode.FieldGroupID: - m.ResetGroupID() - return nil - case redeemcode.FieldValidityDays: - m.ResetValidityDays() + case setting.FieldUpdatedAt: + m.ResetUpdatedAt() return nil } - return fmt.Errorf("unknown RedeemCode field %s", name) + return fmt.Errorf("unknown Setting field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *RedeemCodeMutation) AddedEdges() []string { - edges := make([]string, 0, 2) - if m.user != nil { - edges = append(edges, redeemcode.EdgeUser) - } - if m.group != nil { - edges = append(edges, redeemcode.EdgeGroup) - } +func (m *SettingMutation) AddedEdges() []string { + edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *RedeemCodeMutation) AddedIDs(name string) []ent.Value { - switch name { - case redeemcode.EdgeUser: - if id := m.user; id != nil { - return []ent.Value{*id} - } - case redeemcode.EdgeGroup: - if id := m.group; id != nil { - return []ent.Value{*id} - } - } +func (m *SettingMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *RedeemCodeMutation) RemovedEdges() []string { - edges := make([]string, 0, 2) +func (m *SettingMutation) RemovedEdges() []string { + edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. -func (m *RedeemCodeMutation) RemovedIDs(name string) []ent.Value { +func (m *SettingMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *RedeemCodeMutation) ClearedEdges() []string { - edges := make([]string, 0, 2) - if m.cleareduser { - edges = append(edges, redeemcode.EdgeUser) - } - if m.clearedgroup { - edges = append(edges, redeemcode.EdgeGroup) - } +func (m *SettingMutation) ClearedEdges() []string { + edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *RedeemCodeMutation) EdgeCleared(name string) bool { - switch name { - case redeemcode.EdgeUser: - return m.cleareduser - case redeemcode.EdgeGroup: - return m.clearedgroup - } +func (m *SettingMutation) EdgeCleared(name string) bool { 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 *RedeemCodeMutation) ClearEdge(name string) error { - switch name { - case redeemcode.EdgeUser: - m.ClearUser() - return nil - case redeemcode.EdgeGroup: - m.ClearGroup() - return nil - } - return fmt.Errorf("unknown RedeemCode unique edge %s", name) +func (m *SettingMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown Setting 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 *RedeemCodeMutation) ResetEdge(name string) error { - switch name { - case redeemcode.EdgeUser: - m.ResetUser() - return nil - case redeemcode.EdgeGroup: - m.ResetGroup() - return nil - } - return fmt.Errorf("unknown RedeemCode edge %s", name) +func (m *SettingMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown Setting edge %s", name) } -// SecuritySecretMutation represents an operation that mutates the SecuritySecret nodes in the graph. -type SecuritySecretMutation struct { +// SubscriptionPlanMutation represents an operation that mutates the SubscriptionPlan nodes in the graph. +type SubscriptionPlanMutation struct { config - op Op - typ string - id *int64 - created_at *time.Time - updated_at *time.Time - key *string - value *string - clearedFields map[string]struct{} - done bool - oldValue func(context.Context) (*SecuritySecret, error) - predicates []predicate.SecuritySecret + op Op + typ string + id *int64 + group_id *int64 + addgroup_id *int64 + name *string + description *string + price *float64 + addprice *float64 + original_price *float64 + addoriginal_price *float64 + validity_days *int + addvalidity_days *int + validity_unit *string + features *string + product_name *string + for_sale *bool + sort_order *int + addsort_order *int + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*SubscriptionPlan, error) + predicates []predicate.SubscriptionPlan } -var _ ent.Mutation = (*SecuritySecretMutation)(nil) +var _ ent.Mutation = (*SubscriptionPlanMutation)(nil) -// securitysecretOption allows management of the mutation configuration using functional options. -type securitysecretOption func(*SecuritySecretMutation) +// subscriptionplanOption allows management of the mutation configuration using functional options. +type subscriptionplanOption func(*SubscriptionPlanMutation) -// newSecuritySecretMutation creates new mutation for the SecuritySecret entity. -func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *SecuritySecretMutation { - m := &SecuritySecretMutation{ +// newSubscriptionPlanMutation creates new mutation for the SubscriptionPlan entity. +func newSubscriptionPlanMutation(c config, op Op, opts ...subscriptionplanOption) *SubscriptionPlanMutation { + m := &SubscriptionPlanMutation{ config: c, op: op, - typ: TypeSecuritySecret, + typ: TypeSubscriptionPlan, clearedFields: make(map[string]struct{}), } for _, opt := range opts { @@ -15856,20 +21204,20 @@ func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *S return m } -// withSecuritySecretID sets the ID field of the mutation. -func withSecuritySecretID(id int64) securitysecretOption { - return func(m *SecuritySecretMutation) { +// withSubscriptionPlanID sets the ID field of the mutation. +func withSubscriptionPlanID(id int64) subscriptionplanOption { + return func(m *SubscriptionPlanMutation) { var ( err error once sync.Once - value *SecuritySecret + value *SubscriptionPlan ) - m.oldValue = func(ctx context.Context) (*SecuritySecret, error) { + m.oldValue = func(ctx context.Context) (*SubscriptionPlan, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { - value, err = m.Client().SecuritySecret.Get(ctx, id) + value, err = m.Client().SubscriptionPlan.Get(ctx, id) } }) return value, err @@ -15878,10 +21226,10 @@ func withSecuritySecretID(id int64) securitysecretOption { } } -// withSecuritySecret sets the old SecuritySecret of the mutation. -func withSecuritySecret(node *SecuritySecret) securitysecretOption { - return func(m *SecuritySecretMutation) { - m.oldValue = func(context.Context) (*SecuritySecret, error) { +// withSubscriptionPlan sets the old SubscriptionPlan of the mutation. +func withSubscriptionPlan(node *SubscriptionPlan) subscriptionplanOption { + return func(m *SubscriptionPlanMutation) { + m.oldValue = func(context.Context) (*SubscriptionPlan, error) { return node, nil } m.id = &node.ID @@ -15890,7 +21238,7 @@ func withSecuritySecret(node *SecuritySecret) securitysecretOption { // 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 SecuritySecretMutation) Client() *Client { +func (m SubscriptionPlanMutation) Client() *Client { client := &Client{config: m.config} client.init() return client @@ -15898,7 +21246,7 @@ func (m SecuritySecretMutation) Client() *Client { // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. -func (m SecuritySecretMutation) Tx() (*Tx, error) { +func (m SubscriptionPlanMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("ent: mutation is not running in a transaction") } @@ -15909,7 +21257,7 @@ func (m SecuritySecretMutation) Tx() (*Tx, error) { // 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 *SecuritySecretMutation) ID() (id int64, exists bool) { +func (m *SubscriptionPlanMutation) ID() (id int64, exists bool) { if m.id == nil { return } @@ -15920,7 +21268,7 @@ func (m *SecuritySecretMutation) ID() (id int64, exists bool) { // 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 *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) { +func (m *SubscriptionPlanMutation) IDs(ctx context.Context) ([]int64, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() @@ -15929,578 +21277,565 @@ func (m *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) { } fallthrough case m.op.Is(OpUpdate | OpDelete): - return m.Client().SecuritySecret.Query().Where(m.predicates...).IDs(ctx) + return m.Client().SubscriptionPlan.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } -// SetCreatedAt sets the "created_at" field. -func (m *SecuritySecretMutation) SetCreatedAt(t time.Time) { - m.created_at = &t +// SetGroupID sets the "group_id" field. +func (m *SubscriptionPlanMutation) SetGroupID(i int64) { + m.group_id = &i + m.addgroup_id = nil } -// CreatedAt returns the value of the "created_at" field in the mutation. -func (m *SecuritySecretMutation) CreatedAt() (r time.Time, exists bool) { - v := m.created_at +// GroupID returns the value of the "group_id" field in the mutation. +func (m *SubscriptionPlanMutation) GroupID() (r int64, exists bool) { + v := m.group_id if v == nil { return } return *v, true } -// OldCreatedAt returns the old "created_at" field's value of the SecuritySecret entity. -// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database. +// OldGroupID returns the old "group_id" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SecuritySecretMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { +func (m *SubscriptionPlanMutation) OldGroupID(ctx context.Context) (v int64, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + return v, errors.New("OldGroupID 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") + return v, errors.New("OldGroupID 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 v, fmt.Errorf("querying old value for OldGroupID: %w", err) + } + return oldValue.GroupID, nil +} + +// AddGroupID adds i to the "group_id" field. +func (m *SubscriptionPlanMutation) AddGroupID(i int64) { + if m.addgroup_id != nil { + *m.addgroup_id += i + } else { + m.addgroup_id = &i + } +} + +// AddedGroupID returns the value that was added to the "group_id" field in this mutation. +func (m *SubscriptionPlanMutation) AddedGroupID() (r int64, exists bool) { + v := m.addgroup_id + if v == nil { + return } - return oldValue.CreatedAt, nil + return *v, true } -// ResetCreatedAt resets all changes to the "created_at" field. -func (m *SecuritySecretMutation) ResetCreatedAt() { - m.created_at = nil +// ResetGroupID resets all changes to the "group_id" field. +func (m *SubscriptionPlanMutation) ResetGroupID() { + m.group_id = nil + m.addgroup_id = nil } -// SetUpdatedAt sets the "updated_at" field. -func (m *SecuritySecretMutation) SetUpdatedAt(t time.Time) { - m.updated_at = &t +// SetName sets the "name" field. +func (m *SubscriptionPlanMutation) SetName(s string) { + m.name = &s } -// UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *SecuritySecretMutation) UpdatedAt() (r time.Time, exists bool) { - v := m.updated_at +// Name returns the value of the "name" field in the mutation. +func (m *SubscriptionPlanMutation) Name() (r string, exists bool) { + v := m.name if v == nil { return } return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the SecuritySecret entity. -// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database. +// OldName returns the old "name" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SecuritySecretMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { +func (m *SubscriptionPlanMutation) OldName(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + return v, errors.New("OldName 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") + return v, errors.New("OldName 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 v, fmt.Errorf("querying old value for OldName: %w", err) } - return oldValue.UpdatedAt, nil + return oldValue.Name, nil } -// ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *SecuritySecretMutation) ResetUpdatedAt() { - m.updated_at = nil +// ResetName resets all changes to the "name" field. +func (m *SubscriptionPlanMutation) ResetName() { + m.name = nil } -// SetKey sets the "key" field. -func (m *SecuritySecretMutation) SetKey(s string) { - m.key = &s +// SetDescription sets the "description" field. +func (m *SubscriptionPlanMutation) SetDescription(s string) { + m.description = &s } -// Key returns the value of the "key" field in the mutation. -func (m *SecuritySecretMutation) Key() (r string, exists bool) { - v := m.key +// Description returns the value of the "description" field in the mutation. +func (m *SubscriptionPlanMutation) Description() (r string, exists bool) { + v := m.description if v == nil { return } return *v, true } -// OldKey returns the old "key" field's value of the SecuritySecret entity. -// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database. +// OldDescription returns the old "description" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SecuritySecretMutation) OldKey(ctx context.Context) (v string, err error) { +func (m *SubscriptionPlanMutation) OldDescription(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldKey is only allowed on UpdateOne operations") + return v, errors.New("OldDescription is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldKey requires an ID field in the mutation") + return v, errors.New("OldDescription requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldKey: %w", err) + return v, fmt.Errorf("querying old value for OldDescription: %w", err) } - return oldValue.Key, nil + return oldValue.Description, nil } -// ResetKey resets all changes to the "key" field. -func (m *SecuritySecretMutation) ResetKey() { - m.key = nil +// ResetDescription resets all changes to the "description" field. +func (m *SubscriptionPlanMutation) ResetDescription() { + m.description = nil } -// SetValue sets the "value" field. -func (m *SecuritySecretMutation) SetValue(s string) { - m.value = &s +// SetPrice sets the "price" field. +func (m *SubscriptionPlanMutation) SetPrice(f float64) { + m.price = &f + m.addprice = nil } -// Value returns the value of the "value" field in the mutation. -func (m *SecuritySecretMutation) Value() (r string, exists bool) { - v := m.value +// Price returns the value of the "price" field in the mutation. +func (m *SubscriptionPlanMutation) Price() (r float64, exists bool) { + v := m.price if v == nil { return } return *v, true } -// OldValue returns the old "value" field's value of the SecuritySecret entity. -// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database. +// OldPrice returns the old "price" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SecuritySecretMutation) OldValue(ctx context.Context) (v string, err error) { +func (m *SubscriptionPlanMutation) OldPrice(ctx context.Context) (v float64, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldValue is only allowed on UpdateOne operations") + return v, errors.New("OldPrice is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldValue requires an ID field in the mutation") + return v, errors.New("OldPrice requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldValue: %w", err) + return v, fmt.Errorf("querying old value for OldPrice: %w", err) } - return oldValue.Value, nil + return oldValue.Price, nil } -// ResetValue resets all changes to the "value" field. -func (m *SecuritySecretMutation) ResetValue() { - m.value = nil -} - -// Where appends a list predicates to the SecuritySecretMutation builder. -func (m *SecuritySecretMutation) Where(ps ...predicate.SecuritySecret) { - m.predicates = append(m.predicates, ps...) +// AddPrice adds f to the "price" field. +func (m *SubscriptionPlanMutation) AddPrice(f float64) { + if m.addprice != nil { + *m.addprice += f + } else { + m.addprice = &f + } } -// WhereP appends storage-level predicates to the SecuritySecretMutation builder. Using this method, -// users can use type-assertion to append predicates that do not depend on any generated package. -func (m *SecuritySecretMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.SecuritySecret, len(ps)) - for i := range ps { - p[i] = ps[i] +// AddedPrice returns the value that was added to the "price" field in this mutation. +func (m *SubscriptionPlanMutation) AddedPrice() (r float64, exists bool) { + v := m.addprice + if v == nil { + return } - m.Where(p...) + return *v, true } -// Op returns the operation name. -func (m *SecuritySecretMutation) Op() Op { - return m.op +// ResetPrice resets all changes to the "price" field. +func (m *SubscriptionPlanMutation) ResetPrice() { + m.price = nil + m.addprice = nil } -// SetOp allows setting the mutation operation. -func (m *SecuritySecretMutation) SetOp(op Op) { - m.op = op +// SetOriginalPrice sets the "original_price" field. +func (m *SubscriptionPlanMutation) SetOriginalPrice(f float64) { + m.original_price = &f + m.addoriginal_price = nil } -// Type returns the node type of this mutation (SecuritySecret). -func (m *SecuritySecretMutation) Type() string { - return m.typ +// OriginalPrice returns the value of the "original_price" field in the mutation. +func (m *SubscriptionPlanMutation) OriginalPrice() (r float64, exists bool) { + v := m.original_price + if v == nil { + return + } + return *v, true } -// 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 *SecuritySecretMutation) Fields() []string { - fields := make([]string, 0, 4) - if m.created_at != nil { - fields = append(fields, securitysecret.FieldCreatedAt) - } - if m.updated_at != nil { - fields = append(fields, securitysecret.FieldUpdatedAt) - } - if m.key != nil { - fields = append(fields, securitysecret.FieldKey) +// OldOriginalPrice returns the old "original_price" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SubscriptionPlanMutation) OldOriginalPrice(ctx context.Context) (v *float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOriginalPrice is only allowed on UpdateOne operations") } - if m.value != nil { - fields = append(fields, securitysecret.FieldValue) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOriginalPrice requires an ID field in the mutation") } - 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 *SecuritySecretMutation) Field(name string) (ent.Value, bool) { - switch name { - case securitysecret.FieldCreatedAt: - return m.CreatedAt() - case securitysecret.FieldUpdatedAt: - return m.UpdatedAt() - case securitysecret.FieldKey: - return m.Key() - case securitysecret.FieldValue: - return m.Value() + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOriginalPrice: %w", err) } - return nil, false + return oldValue.OriginalPrice, nil } -// 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 *SecuritySecretMutation) OldField(ctx context.Context, name string) (ent.Value, error) { - switch name { - case securitysecret.FieldCreatedAt: - return m.OldCreatedAt(ctx) - case securitysecret.FieldUpdatedAt: - return m.OldUpdatedAt(ctx) - case securitysecret.FieldKey: - return m.OldKey(ctx) - case securitysecret.FieldValue: - return m.OldValue(ctx) +// AddOriginalPrice adds f to the "original_price" field. +func (m *SubscriptionPlanMutation) AddOriginalPrice(f float64) { + if m.addoriginal_price != nil { + *m.addoriginal_price += f + } else { + m.addoriginal_price = &f } - return nil, fmt.Errorf("unknown SecuritySecret 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 *SecuritySecretMutation) SetField(name string, value ent.Value) error { - switch name { - case securitysecret.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 securitysecret.FieldUpdatedAt: - v, ok := value.(time.Time) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetUpdatedAt(v) - return nil - case securitysecret.FieldKey: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetKey(v) - return nil - case securitysecret.FieldValue: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetValue(v) - return nil +// AddedOriginalPrice returns the value that was added to the "original_price" field in this mutation. +func (m *SubscriptionPlanMutation) AddedOriginalPrice() (r float64, exists bool) { + v := m.addoriginal_price + if v == nil { + return } - return fmt.Errorf("unknown SecuritySecret field %s", name) -} - -// AddedFields returns all numeric fields that were incremented/decremented during -// this mutation. -func (m *SecuritySecretMutation) AddedFields() []string { - return nil + return *v, true } -// 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 *SecuritySecretMutation) AddedField(name string) (ent.Value, bool) { - return nil, false +// ClearOriginalPrice clears the value of the "original_price" field. +func (m *SubscriptionPlanMutation) ClearOriginalPrice() { + m.original_price = nil + m.addoriginal_price = nil + m.clearedFields[subscriptionplan.FieldOriginalPrice] = struct{}{} } -// 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 *SecuritySecretMutation) AddField(name string, value ent.Value) error { - switch name { - } - return fmt.Errorf("unknown SecuritySecret numeric field %s", name) +// OriginalPriceCleared returns if the "original_price" field was cleared in this mutation. +func (m *SubscriptionPlanMutation) OriginalPriceCleared() bool { + _, ok := m.clearedFields[subscriptionplan.FieldOriginalPrice] + return ok } -// ClearedFields returns all nullable fields that were cleared during this -// mutation. -func (m *SecuritySecretMutation) ClearedFields() []string { - return nil +// ResetOriginalPrice resets all changes to the "original_price" field. +func (m *SubscriptionPlanMutation) ResetOriginalPrice() { + m.original_price = nil + m.addoriginal_price = nil + delete(m.clearedFields, subscriptionplan.FieldOriginalPrice) } -// FieldCleared returns a boolean indicating if a field with the given name was -// cleared in this mutation. -func (m *SecuritySecretMutation) FieldCleared(name string) bool { - _, ok := m.clearedFields[name] - return ok +// SetValidityDays sets the "validity_days" field. +func (m *SubscriptionPlanMutation) SetValidityDays(i int) { + m.validity_days = &i + m.addvalidity_days = nil } -// 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 *SecuritySecretMutation) ClearField(name string) error { - return fmt.Errorf("unknown SecuritySecret nullable field %s", name) +// ValidityDays returns the value of the "validity_days" field in the mutation. +func (m *SubscriptionPlanMutation) ValidityDays() (r int, exists bool) { + v := m.validity_days + if v == nil { + return + } + return *v, true } -// 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 *SecuritySecretMutation) ResetField(name string) error { - switch name { - case securitysecret.FieldCreatedAt: - m.ResetCreatedAt() - return nil - case securitysecret.FieldUpdatedAt: - m.ResetUpdatedAt() - return nil - case securitysecret.FieldKey: - m.ResetKey() - return nil - case securitysecret.FieldValue: - m.ResetValue() - return nil +// OldValidityDays returns the old "validity_days" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SubscriptionPlanMutation) OldValidityDays(ctx context.Context) (v int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldValidityDays is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldValidityDays requires an ID field in the mutation") } - return fmt.Errorf("unknown SecuritySecret field %s", name) + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldValidityDays: %w", err) + } + return oldValue.ValidityDays, nil } -// AddedEdges returns all edge names that were set/added in this mutation. -func (m *SecuritySecretMutation) AddedEdges() []string { - edges := make([]string, 0, 0) - return edges +// AddValidityDays adds i to the "validity_days" field. +func (m *SubscriptionPlanMutation) AddValidityDays(i int) { + if m.addvalidity_days != nil { + *m.addvalidity_days += i + } else { + m.addvalidity_days = &i + } } -// AddedIDs returns all IDs (to other nodes) that were added for the given edge -// name in this mutation. -func (m *SecuritySecretMutation) AddedIDs(name string) []ent.Value { - return nil +// AddedValidityDays returns the value that was added to the "validity_days" field in this mutation. +func (m *SubscriptionPlanMutation) AddedValidityDays() (r int, exists bool) { + v := m.addvalidity_days + if v == nil { + return + } + return *v, true } -// RemovedEdges returns all edge names that were removed in this mutation. -func (m *SecuritySecretMutation) RemovedEdges() []string { - edges := make([]string, 0, 0) - return edges +// ResetValidityDays resets all changes to the "validity_days" field. +func (m *SubscriptionPlanMutation) ResetValidityDays() { + m.validity_days = nil + m.addvalidity_days = nil } -// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with -// the given name in this mutation. -func (m *SecuritySecretMutation) RemovedIDs(name string) []ent.Value { - return nil +// SetValidityUnit sets the "validity_unit" field. +func (m *SubscriptionPlanMutation) SetValidityUnit(s string) { + m.validity_unit = &s } -// ClearedEdges returns all edge names that were cleared in this mutation. -func (m *SecuritySecretMutation) ClearedEdges() []string { - edges := make([]string, 0, 0) - return edges +// ValidityUnit returns the value of the "validity_unit" field in the mutation. +func (m *SubscriptionPlanMutation) ValidityUnit() (r string, exists bool) { + v := m.validity_unit + if v == nil { + return + } + return *v, true } -// EdgeCleared returns a boolean which indicates if the edge with the given name -// was cleared in this mutation. -func (m *SecuritySecretMutation) EdgeCleared(name string) bool { - return false +// OldValidityUnit returns the old "validity_unit" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SubscriptionPlanMutation) OldValidityUnit(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldValidityUnit is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldValidityUnit requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldValidityUnit: %w", err) + } + return oldValue.ValidityUnit, nil } -// 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 *SecuritySecretMutation) ClearEdge(name string) error { - return fmt.Errorf("unknown SecuritySecret unique edge %s", name) +// ResetValidityUnit resets all changes to the "validity_unit" field. +func (m *SubscriptionPlanMutation) ResetValidityUnit() { + m.validity_unit = nil } -// 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 *SecuritySecretMutation) ResetEdge(name string) error { - return fmt.Errorf("unknown SecuritySecret edge %s", name) +// SetFeatures sets the "features" field. +func (m *SubscriptionPlanMutation) SetFeatures(s string) { + m.features = &s } -// SettingMutation represents an operation that mutates the Setting nodes in the graph. -type SettingMutation struct { - config - op Op - typ string - id *int64 - key *string - value *string - updated_at *time.Time - clearedFields map[string]struct{} - done bool - oldValue func(context.Context) (*Setting, error) - predicates []predicate.Setting +// Features returns the value of the "features" field in the mutation. +func (m *SubscriptionPlanMutation) Features() (r string, exists bool) { + v := m.features + if v == nil { + return + } + return *v, true } -var _ ent.Mutation = (*SettingMutation)(nil) - -// settingOption allows management of the mutation configuration using functional options. -type settingOption func(*SettingMutation) - -// newSettingMutation creates new mutation for the Setting entity. -func newSettingMutation(c config, op Op, opts ...settingOption) *SettingMutation { - m := &SettingMutation{ - config: c, - op: op, - typ: TypeSetting, - clearedFields: make(map[string]struct{}), +// OldFeatures returns the old "features" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SubscriptionPlanMutation) OldFeatures(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFeatures is only allowed on UpdateOne operations") } - for _, opt := range opts { - opt(m) + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFeatures requires an ID field in the mutation") } - return m + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFeatures: %w", err) + } + return oldValue.Features, nil } -// withSettingID sets the ID field of the mutation. -func withSettingID(id int64) settingOption { - return func(m *SettingMutation) { - var ( - err error - once sync.Once - value *Setting - ) - m.oldValue = func(ctx context.Context) (*Setting, error) { - once.Do(func() { - if m.done { - err = errors.New("querying old values post mutation is not allowed") - } else { - value, err = m.Client().Setting.Get(ctx, id) - } - }) - return value, err - } - m.id = &id +// ResetFeatures resets all changes to the "features" field. +func (m *SubscriptionPlanMutation) ResetFeatures() { + m.features = nil +} + +// SetProductName sets the "product_name" field. +func (m *SubscriptionPlanMutation) SetProductName(s string) { + m.product_name = &s +} + +// ProductName returns the value of the "product_name" field in the mutation. +func (m *SubscriptionPlanMutation) ProductName() (r string, exists bool) { + v := m.product_name + if v == nil { + return } + return *v, true } -// withSetting sets the old Setting of the mutation. -func withSetting(node *Setting) settingOption { - return func(m *SettingMutation) { - m.oldValue = func(context.Context) (*Setting, error) { - return node, nil - } - m.id = &node.ID +// OldProductName returns the old "product_name" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SubscriptionPlanMutation) OldProductName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldProductName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldProductName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldProductName: %w", err) } + return oldValue.ProductName, nil } -// 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 SettingMutation) Client() *Client { - client := &Client{config: m.config} - client.init() - return client +// ResetProductName resets all changes to the "product_name" field. +func (m *SubscriptionPlanMutation) ResetProductName() { + m.product_name = nil } -// Tx returns an `ent.Tx` for mutations that were executed in transactions; -// it returns an error otherwise. -func (m SettingMutation) 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 +// SetForSale sets the "for_sale" field. +func (m *SubscriptionPlanMutation) SetForSale(b bool) { + m.for_sale = &b } -// 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 *SettingMutation) ID() (id int64, exists bool) { - if m.id == nil { +// ForSale returns the value of the "for_sale" field in the mutation. +func (m *SubscriptionPlanMutation) ForSale() (r bool, exists bool) { + v := m.for_sale + if v == nil { return } - return *m.id, true + return *v, 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 *SettingMutation) 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().Setting.Query().Where(m.predicates...).IDs(ctx) - default: - return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) +// OldForSale returns the old "for_sale" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SubscriptionPlanMutation) OldForSale(ctx context.Context) (v bool, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldForSale is only allowed on UpdateOne operations") } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldForSale requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldForSale: %w", err) + } + return oldValue.ForSale, nil } -// SetKey sets the "key" field. -func (m *SettingMutation) SetKey(s string) { - m.key = &s +// ResetForSale resets all changes to the "for_sale" field. +func (m *SubscriptionPlanMutation) ResetForSale() { + m.for_sale = nil } -// Key returns the value of the "key" field in the mutation. -func (m *SettingMutation) Key() (r string, exists bool) { - v := m.key +// SetSortOrder sets the "sort_order" field. +func (m *SubscriptionPlanMutation) SetSortOrder(i int) { + m.sort_order = &i + m.addsort_order = nil +} + +// SortOrder returns the value of the "sort_order" field in the mutation. +func (m *SubscriptionPlanMutation) SortOrder() (r int, exists bool) { + v := m.sort_order if v == nil { return } return *v, true } -// OldKey returns the old "key" field's value of the Setting entity. -// If the Setting object wasn't provided to the builder, the object is fetched from the database. +// OldSortOrder returns the old "sort_order" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SettingMutation) OldKey(ctx context.Context) (v string, err error) { +func (m *SubscriptionPlanMutation) OldSortOrder(ctx context.Context) (v int, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldKey is only allowed on UpdateOne operations") + return v, errors.New("OldSortOrder is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldKey requires an ID field in the mutation") + return v, errors.New("OldSortOrder requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldKey: %w", err) + return v, fmt.Errorf("querying old value for OldSortOrder: %w", err) } - return oldValue.Key, nil + return oldValue.SortOrder, nil } -// ResetKey resets all changes to the "key" field. -func (m *SettingMutation) ResetKey() { - m.key = nil +// AddSortOrder adds i to the "sort_order" field. +func (m *SubscriptionPlanMutation) AddSortOrder(i int) { + if m.addsort_order != nil { + *m.addsort_order += i + } else { + m.addsort_order = &i + } } -// SetValue sets the "value" field. -func (m *SettingMutation) SetValue(s string) { - m.value = &s +// AddedSortOrder returns the value that was added to the "sort_order" field in this mutation. +func (m *SubscriptionPlanMutation) AddedSortOrder() (r int, exists bool) { + v := m.addsort_order + if v == nil { + return + } + return *v, true } -// Value returns the value of the "value" field in the mutation. -func (m *SettingMutation) Value() (r string, exists bool) { - v := m.value +// ResetSortOrder resets all changes to the "sort_order" field. +func (m *SubscriptionPlanMutation) ResetSortOrder() { + m.sort_order = nil + m.addsort_order = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *SubscriptionPlanMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *SubscriptionPlanMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at if v == nil { return } return *v, true } -// OldValue returns the old "value" field's value of the Setting entity. -// If the Setting object wasn't provided to the builder, the object is fetched from the database. +// OldCreatedAt returns the old "created_at" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SettingMutation) OldValue(ctx context.Context) (v string, err error) { +func (m *SubscriptionPlanMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldValue is only allowed on UpdateOne operations") + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldValue requires an ID field in the mutation") + 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 OldValue: %w", err) + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } - return oldValue.Value, nil + return oldValue.CreatedAt, nil } -// ResetValue resets all changes to the "value" field. -func (m *SettingMutation) ResetValue() { - m.value = nil +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *SubscriptionPlanMutation) ResetCreatedAt() { + m.created_at = nil } // SetUpdatedAt sets the "updated_at" field. -func (m *SettingMutation) SetUpdatedAt(t time.Time) { +func (m *SubscriptionPlanMutation) SetUpdatedAt(t time.Time) { m.updated_at = &t } // UpdatedAt returns the value of the "updated_at" field in the mutation. -func (m *SettingMutation) UpdatedAt() (r time.Time, exists bool) { +func (m *SubscriptionPlanMutation) UpdatedAt() (r time.Time, exists bool) { v := m.updated_at if v == nil { return @@ -16508,10 +21843,10 @@ func (m *SettingMutation) UpdatedAt() (r time.Time, exists bool) { return *v, true } -// OldUpdatedAt returns the old "updated_at" field's value of the Setting entity. -// If the Setting object wasn't provided to the builder, the object is fetched from the database. +// OldUpdatedAt returns the old "updated_at" field's value of the SubscriptionPlan entity. +// If the SubscriptionPlan 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 *SettingMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { +func (m *SubscriptionPlanMutation) 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") } @@ -16526,19 +21861,19 @@ func (m *SettingMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err er } // ResetUpdatedAt resets all changes to the "updated_at" field. -func (m *SettingMutation) ResetUpdatedAt() { +func (m *SubscriptionPlanMutation) ResetUpdatedAt() { m.updated_at = nil } -// Where appends a list predicates to the SettingMutation builder. -func (m *SettingMutation) Where(ps ...predicate.Setting) { +// Where appends a list predicates to the SubscriptionPlanMutation builder. +func (m *SubscriptionPlanMutation) Where(ps ...predicate.SubscriptionPlan) { m.predicates = append(m.predicates, ps...) } -// WhereP appends storage-level predicates to the SettingMutation builder. Using this method, +// WhereP appends storage-level predicates to the SubscriptionPlanMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. -func (m *SettingMutation) WhereP(ps ...func(*sql.Selector)) { - p := make([]predicate.Setting, len(ps)) +func (m *SubscriptionPlanMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.SubscriptionPlan, len(ps)) for i := range ps { p[i] = ps[i] } @@ -16546,33 +21881,63 @@ func (m *SettingMutation) WhereP(ps ...func(*sql.Selector)) { } // Op returns the operation name. -func (m *SettingMutation) Op() Op { +func (m *SubscriptionPlanMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. -func (m *SettingMutation) SetOp(op Op) { +func (m *SubscriptionPlanMutation) SetOp(op Op) { m.op = op } -// Type returns the node type of this mutation (Setting). -func (m *SettingMutation) Type() string { +// Type returns the node type of this mutation (SubscriptionPlan). +func (m *SubscriptionPlanMutation) 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 *SettingMutation) Fields() []string { - fields := make([]string, 0, 3) - if m.key != nil { - fields = append(fields, setting.FieldKey) +func (m *SubscriptionPlanMutation) Fields() []string { + fields := make([]string, 0, 13) + if m.group_id != nil { + fields = append(fields, subscriptionplan.FieldGroupID) } - if m.value != nil { - fields = append(fields, setting.FieldValue) + if m.name != nil { + fields = append(fields, subscriptionplan.FieldName) + } + if m.description != nil { + fields = append(fields, subscriptionplan.FieldDescription) + } + if m.price != nil { + fields = append(fields, subscriptionplan.FieldPrice) + } + if m.original_price != nil { + fields = append(fields, subscriptionplan.FieldOriginalPrice) + } + if m.validity_days != nil { + fields = append(fields, subscriptionplan.FieldValidityDays) + } + if m.validity_unit != nil { + fields = append(fields, subscriptionplan.FieldValidityUnit) + } + if m.features != nil { + fields = append(fields, subscriptionplan.FieldFeatures) + } + if m.product_name != nil { + fields = append(fields, subscriptionplan.FieldProductName) + } + if m.for_sale != nil { + fields = append(fields, subscriptionplan.FieldForSale) + } + if m.sort_order != nil { + fields = append(fields, subscriptionplan.FieldSortOrder) + } + if m.created_at != nil { + fields = append(fields, subscriptionplan.FieldCreatedAt) } if m.updated_at != nil { - fields = append(fields, setting.FieldUpdatedAt) + fields = append(fields, subscriptionplan.FieldUpdatedAt) } return fields } @@ -16580,13 +21945,33 @@ func (m *SettingMutation) Fields() []string { // 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 *SettingMutation) Field(name string) (ent.Value, bool) { +func (m *SubscriptionPlanMutation) Field(name string) (ent.Value, bool) { switch name { - case setting.FieldKey: - return m.Key() - case setting.FieldValue: - return m.Value() - case setting.FieldUpdatedAt: + case subscriptionplan.FieldGroupID: + return m.GroupID() + case subscriptionplan.FieldName: + return m.Name() + case subscriptionplan.FieldDescription: + return m.Description() + case subscriptionplan.FieldPrice: + return m.Price() + case subscriptionplan.FieldOriginalPrice: + return m.OriginalPrice() + case subscriptionplan.FieldValidityDays: + return m.ValidityDays() + case subscriptionplan.FieldValidityUnit: + return m.ValidityUnit() + case subscriptionplan.FieldFeatures: + return m.Features() + case subscriptionplan.FieldProductName: + return m.ProductName() + case subscriptionplan.FieldForSale: + return m.ForSale() + case subscriptionplan.FieldSortOrder: + return m.SortOrder() + case subscriptionplan.FieldCreatedAt: + return m.CreatedAt() + case subscriptionplan.FieldUpdatedAt: return m.UpdatedAt() } return nil, false @@ -16595,38 +21980,128 @@ func (m *SettingMutation) Field(name string) (ent.Value, bool) { // 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 *SettingMutation) OldField(ctx context.Context, name string) (ent.Value, error) { +func (m *SubscriptionPlanMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { - case setting.FieldKey: - return m.OldKey(ctx) - case setting.FieldValue: - return m.OldValue(ctx) - case setting.FieldUpdatedAt: + case subscriptionplan.FieldGroupID: + return m.OldGroupID(ctx) + case subscriptionplan.FieldName: + return m.OldName(ctx) + case subscriptionplan.FieldDescription: + return m.OldDescription(ctx) + case subscriptionplan.FieldPrice: + return m.OldPrice(ctx) + case subscriptionplan.FieldOriginalPrice: + return m.OldOriginalPrice(ctx) + case subscriptionplan.FieldValidityDays: + return m.OldValidityDays(ctx) + case subscriptionplan.FieldValidityUnit: + return m.OldValidityUnit(ctx) + case subscriptionplan.FieldFeatures: + return m.OldFeatures(ctx) + case subscriptionplan.FieldProductName: + return m.OldProductName(ctx) + case subscriptionplan.FieldForSale: + return m.OldForSale(ctx) + case subscriptionplan.FieldSortOrder: + return m.OldSortOrder(ctx) + case subscriptionplan.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case subscriptionplan.FieldUpdatedAt: return m.OldUpdatedAt(ctx) } - return nil, fmt.Errorf("unknown Setting field %s", name) + return nil, fmt.Errorf("unknown SubscriptionPlan 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 *SettingMutation) SetField(name string, value ent.Value) error { +func (m *SubscriptionPlanMutation) SetField(name string, value ent.Value) error { switch name { - case setting.FieldKey: + case subscriptionplan.FieldGroupID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetGroupID(v) + return nil + case subscriptionplan.FieldName: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetKey(v) + m.SetName(v) return nil - case setting.FieldValue: + case subscriptionplan.FieldDescription: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetValue(v) + m.SetDescription(v) return nil - case setting.FieldUpdatedAt: + case subscriptionplan.FieldPrice: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetPrice(v) + return nil + case subscriptionplan.FieldOriginalPrice: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOriginalPrice(v) + return nil + case subscriptionplan.FieldValidityDays: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetValidityDays(v) + return nil + case subscriptionplan.FieldValidityUnit: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetValidityUnit(v) + return nil + case subscriptionplan.FieldFeatures: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFeatures(v) + return nil + case subscriptionplan.FieldProductName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetProductName(v) + return nil + case subscriptionplan.FieldForSale: + v, ok := value.(bool) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetForSale(v) + return nil + case subscriptionplan.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSortOrder(v) + return nil + case subscriptionplan.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 subscriptionplan.FieldUpdatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) @@ -16634,113 +22109,215 @@ func (m *SettingMutation) SetField(name string, value ent.Value) error { m.SetUpdatedAt(v) return nil } - return fmt.Errorf("unknown Setting field %s", name) + return fmt.Errorf("unknown SubscriptionPlan field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. -func (m *SettingMutation) AddedFields() []string { - return nil +func (m *SubscriptionPlanMutation) AddedFields() []string { + var fields []string + if m.addgroup_id != nil { + fields = append(fields, subscriptionplan.FieldGroupID) + } + if m.addprice != nil { + fields = append(fields, subscriptionplan.FieldPrice) + } + if m.addoriginal_price != nil { + fields = append(fields, subscriptionplan.FieldOriginalPrice) + } + if m.addvalidity_days != nil { + fields = append(fields, subscriptionplan.FieldValidityDays) + } + if m.addsort_order != nil { + fields = append(fields, subscriptionplan.FieldSortOrder) + } + 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 *SettingMutation) AddedField(name string) (ent.Value, bool) { +func (m *SubscriptionPlanMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case subscriptionplan.FieldGroupID: + return m.AddedGroupID() + case subscriptionplan.FieldPrice: + return m.AddedPrice() + case subscriptionplan.FieldOriginalPrice: + return m.AddedOriginalPrice() + case subscriptionplan.FieldValidityDays: + return m.AddedValidityDays() + case subscriptionplan.FieldSortOrder: + return m.AddedSortOrder() + } 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 *SettingMutation) AddField(name string, value ent.Value) error { +func (m *SubscriptionPlanMutation) AddField(name string, value ent.Value) error { switch name { + case subscriptionplan.FieldGroupID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddGroupID(v) + return nil + case subscriptionplan.FieldPrice: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddPrice(v) + return nil + case subscriptionplan.FieldOriginalPrice: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddOriginalPrice(v) + return nil + case subscriptionplan.FieldValidityDays: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddValidityDays(v) + return nil + case subscriptionplan.FieldSortOrder: + v, ok := value.(int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddSortOrder(v) + return nil } - return fmt.Errorf("unknown Setting numeric field %s", name) + return fmt.Errorf("unknown SubscriptionPlan numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. -func (m *SettingMutation) ClearedFields() []string { - return nil +func (m *SubscriptionPlanMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(subscriptionplan.FieldOriginalPrice) { + fields = append(fields, subscriptionplan.FieldOriginalPrice) + } + return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. -func (m *SettingMutation) FieldCleared(name string) bool { +func (m *SubscriptionPlanMutation) 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 *SettingMutation) ClearField(name string) error { - return fmt.Errorf("unknown Setting nullable field %s", name) +func (m *SubscriptionPlanMutation) ClearField(name string) error { + switch name { + case subscriptionplan.FieldOriginalPrice: + m.ClearOriginalPrice() + return nil + } + return fmt.Errorf("unknown SubscriptionPlan 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 *SettingMutation) ResetField(name string) error { +func (m *SubscriptionPlanMutation) ResetField(name string) error { switch name { - case setting.FieldKey: - m.ResetKey() + case subscriptionplan.FieldGroupID: + m.ResetGroupID() return nil - case setting.FieldValue: - m.ResetValue() + case subscriptionplan.FieldName: + m.ResetName() return nil - case setting.FieldUpdatedAt: + case subscriptionplan.FieldDescription: + m.ResetDescription() + return nil + case subscriptionplan.FieldPrice: + m.ResetPrice() + return nil + case subscriptionplan.FieldOriginalPrice: + m.ResetOriginalPrice() + return nil + case subscriptionplan.FieldValidityDays: + m.ResetValidityDays() + return nil + case subscriptionplan.FieldValidityUnit: + m.ResetValidityUnit() + return nil + case subscriptionplan.FieldFeatures: + m.ResetFeatures() + return nil + case subscriptionplan.FieldProductName: + m.ResetProductName() + return nil + case subscriptionplan.FieldForSale: + m.ResetForSale() + return nil + case subscriptionplan.FieldSortOrder: + m.ResetSortOrder() + return nil + case subscriptionplan.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case subscriptionplan.FieldUpdatedAt: m.ResetUpdatedAt() return nil } - return fmt.Errorf("unknown Setting field %s", name) + return fmt.Errorf("unknown SubscriptionPlan field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. -func (m *SettingMutation) AddedEdges() []string { +func (m *SubscriptionPlanMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. -func (m *SettingMutation) AddedIDs(name string) []ent.Value { +func (m *SubscriptionPlanMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. -func (m *SettingMutation) RemovedEdges() []string { +func (m *SubscriptionPlanMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. -func (m *SettingMutation) RemovedIDs(name string) []ent.Value { +func (m *SubscriptionPlanMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. -func (m *SettingMutation) ClearedEdges() []string { +func (m *SubscriptionPlanMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. -func (m *SettingMutation) EdgeCleared(name string) bool { +func (m *SubscriptionPlanMutation) EdgeCleared(name string) bool { 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 *SettingMutation) ClearEdge(name string) error { - return fmt.Errorf("unknown Setting unique edge %s", name) +func (m *SubscriptionPlanMutation) ClearEdge(name string) error { + return fmt.Errorf("unknown SubscriptionPlan 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 *SettingMutation) ResetEdge(name string) error { - return fmt.Errorf("unknown Setting edge %s", name) +func (m *SubscriptionPlanMutation) ResetEdge(name string) error { + return fmt.Errorf("unknown SubscriptionPlan edge %s", name) } // TLSFingerprintProfileMutation represents an operation that mutates the TLSFingerprintProfile nodes in the graph. @@ -22661,6 +28238,9 @@ type UserMutation struct { promo_code_usages map[int64]struct{} removedpromo_code_usages map[int64]struct{} clearedpromo_code_usages bool + payment_orders map[int64]struct{} + removedpayment_orders map[int64]struct{} + clearedpayment_orders bool done bool oldValue func(context.Context) (*User, error) predicates []predicate.User @@ -23833,6 +29413,60 @@ func (m *UserMutation) ResetPromoCodeUsages() { m.removedpromo_code_usages = nil } +// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by ids. +func (m *UserMutation) AddPaymentOrderIDs(ids ...int64) { + if m.payment_orders == nil { + m.payment_orders = make(map[int64]struct{}) + } + for i := range ids { + m.payment_orders[ids[i]] = struct{}{} + } +} + +// ClearPaymentOrders clears the "payment_orders" edge to the PaymentOrder entity. +func (m *UserMutation) ClearPaymentOrders() { + m.clearedpayment_orders = true +} + +// PaymentOrdersCleared reports if the "payment_orders" edge to the PaymentOrder entity was cleared. +func (m *UserMutation) PaymentOrdersCleared() bool { + return m.clearedpayment_orders +} + +// RemovePaymentOrderIDs removes the "payment_orders" edge to the PaymentOrder entity by IDs. +func (m *UserMutation) RemovePaymentOrderIDs(ids ...int64) { + if m.removedpayment_orders == nil { + m.removedpayment_orders = make(map[int64]struct{}) + } + for i := range ids { + delete(m.payment_orders, ids[i]) + m.removedpayment_orders[ids[i]] = struct{}{} + } +} + +// RemovedPaymentOrders returns the removed IDs of the "payment_orders" edge to the PaymentOrder entity. +func (m *UserMutation) RemovedPaymentOrdersIDs() (ids []int64) { + for id := range m.removedpayment_orders { + ids = append(ids, id) + } + return +} + +// PaymentOrdersIDs returns the "payment_orders" edge IDs in the mutation. +func (m *UserMutation) PaymentOrdersIDs() (ids []int64) { + for id := range m.payment_orders { + ids = append(ids, id) + } + return +} + +// ResetPaymentOrders resets all changes to the "payment_orders" edge. +func (m *UserMutation) ResetPaymentOrders() { + m.payment_orders = nil + m.clearedpayment_orders = false + m.removedpayment_orders = nil +} + // Where appends a list predicates to the UserMutation builder. func (m *UserMutation) Where(ps ...predicate.User) { m.predicates = append(m.predicates, ps...) @@ -24235,7 +29869,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, 9) + edges := make([]string, 0, 10) if m.api_keys != nil { edges = append(edges, user.EdgeAPIKeys) } @@ -24263,6 +29897,9 @@ func (m *UserMutation) AddedEdges() []string { if m.promo_code_usages != nil { edges = append(edges, user.EdgePromoCodeUsages) } + if m.payment_orders != nil { + edges = append(edges, user.EdgePaymentOrders) + } return edges } @@ -24324,13 +29961,19 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgePaymentOrders: + ids := make([]ent.Value, 0, len(m.payment_orders)) + for id := range m.payment_orders { + 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, 9) + edges := make([]string, 0, 10) if m.removedapi_keys != nil { edges = append(edges, user.EdgeAPIKeys) } @@ -24358,6 +30001,9 @@ func (m *UserMutation) RemovedEdges() []string { if m.removedpromo_code_usages != nil { edges = append(edges, user.EdgePromoCodeUsages) } + if m.removedpayment_orders != nil { + edges = append(edges, user.EdgePaymentOrders) + } return edges } @@ -24419,13 +30065,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgePaymentOrders: + ids := make([]ent.Value, 0, len(m.removedpayment_orders)) + for id := range m.removedpayment_orders { + 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, 9) + edges := make([]string, 0, 10) if m.clearedapi_keys { edges = append(edges, user.EdgeAPIKeys) } @@ -24453,6 +30105,9 @@ func (m *UserMutation) ClearedEdges() []string { if m.clearedpromo_code_usages { edges = append(edges, user.EdgePromoCodeUsages) } + if m.clearedpayment_orders { + edges = append(edges, user.EdgePaymentOrders) + } return edges } @@ -24478,6 +30133,8 @@ func (m *UserMutation) EdgeCleared(name string) bool { return m.clearedattribute_values case user.EdgePromoCodeUsages: return m.clearedpromo_code_usages + case user.EdgePaymentOrders: + return m.clearedpayment_orders } return false } @@ -24521,6 +30178,9 @@ func (m *UserMutation) ResetEdge(name string) error { case user.EdgePromoCodeUsages: m.ResetPromoCodeUsages() return nil + case user.EdgePaymentOrders: + m.ResetPaymentOrders() + return nil } return fmt.Errorf("unknown User edge %s", name) } diff --git a/backend/ent/paymentauditlog.go b/backend/ent/paymentauditlog.go new file mode 100644 index 0000000000000000000000000000000000000000..ffcdcfcd95562d08ea78e132c467f3f98b60402f --- /dev/null +++ b/backend/ent/paymentauditlog.go @@ -0,0 +1,150 @@ +// 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/paymentauditlog" +) + +// PaymentAuditLog is the model entity for the PaymentAuditLog schema. +type PaymentAuditLog struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // OrderID holds the value of the "order_id" field. + OrderID string `json:"order_id,omitempty"` + // Action holds the value of the "action" field. + Action string `json:"action,omitempty"` + // Detail holds the value of the "detail" field. + Detail string `json:"detail,omitempty"` + // Operator holds the value of the "operator" field. + Operator string `json:"operator,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*PaymentAuditLog) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case paymentauditlog.FieldID: + values[i] = new(sql.NullInt64) + case paymentauditlog.FieldOrderID, paymentauditlog.FieldAction, paymentauditlog.FieldDetail, paymentauditlog.FieldOperator: + values[i] = new(sql.NullString) + case paymentauditlog.FieldCreatedAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the PaymentAuditLog fields. +func (_m *PaymentAuditLog) 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 paymentauditlog.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 paymentauditlog.FieldOrderID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field order_id", values[i]) + } else if value.Valid { + _m.OrderID = value.String + } + case paymentauditlog.FieldAction: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field action", values[i]) + } else if value.Valid { + _m.Action = value.String + } + case paymentauditlog.FieldDetail: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field detail", values[i]) + } else if value.Valid { + _m.Detail = value.String + } + case paymentauditlog.FieldOperator: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field operator", values[i]) + } else if value.Valid { + _m.Operator = value.String + } + case paymentauditlog.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the PaymentAuditLog. +// This includes values selected through modifiers, order, etc. +func (_m *PaymentAuditLog) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this PaymentAuditLog. +// Note that you need to call PaymentAuditLog.Unwrap() before calling this method if this PaymentAuditLog +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *PaymentAuditLog) Update() *PaymentAuditLogUpdateOne { + return NewPaymentAuditLogClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the PaymentAuditLog 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 *PaymentAuditLog) Unwrap() *PaymentAuditLog { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: PaymentAuditLog is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *PaymentAuditLog) String() string { + var builder strings.Builder + builder.WriteString("PaymentAuditLog(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("order_id=") + builder.WriteString(_m.OrderID) + builder.WriteString(", ") + builder.WriteString("action=") + builder.WriteString(_m.Action) + builder.WriteString(", ") + builder.WriteString("detail=") + builder.WriteString(_m.Detail) + builder.WriteString(", ") + builder.WriteString("operator=") + builder.WriteString(_m.Operator) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// PaymentAuditLogs is a parsable slice of PaymentAuditLog. +type PaymentAuditLogs []*PaymentAuditLog diff --git a/backend/ent/paymentauditlog/paymentauditlog.go b/backend/ent/paymentauditlog/paymentauditlog.go new file mode 100644 index 0000000000000000000000000000000000000000..9d480eef21971edb19687fbcc671cf75bc90c624 --- /dev/null +++ b/backend/ent/paymentauditlog/paymentauditlog.go @@ -0,0 +1,96 @@ +// Code generated by ent, DO NOT EDIT. + +package paymentauditlog + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the paymentauditlog type in the database. + Label = "payment_audit_log" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldOrderID holds the string denoting the order_id field in the database. + FieldOrderID = "order_id" + // FieldAction holds the string denoting the action field in the database. + FieldAction = "action" + // FieldDetail holds the string denoting the detail field in the database. + FieldDetail = "detail" + // FieldOperator holds the string denoting the operator field in the database. + FieldOperator = "operator" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // Table holds the table name of the paymentauditlog in the database. + Table = "payment_audit_logs" +) + +// Columns holds all SQL columns for paymentauditlog fields. +var Columns = []string{ + FieldID, + FieldOrderID, + FieldAction, + FieldDetail, + FieldOperator, + FieldCreatedAt, +} + +// 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 ( + // OrderIDValidator is a validator for the "order_id" field. It is called by the builders before save. + OrderIDValidator func(string) error + // ActionValidator is a validator for the "action" field. It is called by the builders before save. + ActionValidator func(string) error + // DefaultDetail holds the default value on creation for the "detail" field. + DefaultDetail string + // DefaultOperator holds the default value on creation for the "operator" field. + DefaultOperator string + // OperatorValidator is a validator for the "operator" field. It is called by the builders before save. + OperatorValidator func(string) error + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time +) + +// OrderOption defines the ordering options for the PaymentAuditLog 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() +} + +// ByOrderID orders the results by the order_id field. +func ByOrderID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOrderID, opts...).ToFunc() +} + +// ByAction orders the results by the action field. +func ByAction(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAction, opts...).ToFunc() +} + +// ByDetail orders the results by the detail field. +func ByDetail(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDetail, opts...).ToFunc() +} + +// ByOperator orders the results by the operator field. +func ByOperator(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOperator, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} diff --git a/backend/ent/paymentauditlog/where.go b/backend/ent/paymentauditlog/where.go new file mode 100644 index 0000000000000000000000000000000000000000..2fd80a42130d305977fb344d712d74889abd27a2 --- /dev/null +++ b/backend/ent/paymentauditlog/where.go @@ -0,0 +1,395 @@ +// Code generated by ent, DO NOT EDIT. + +package paymentauditlog + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLTE(FieldID, id)) +} + +// OrderID applies equality check predicate on the "order_id" field. It's identical to OrderIDEQ. +func OrderID(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldOrderID, v)) +} + +// Action applies equality check predicate on the "action" field. It's identical to ActionEQ. +func Action(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldAction, v)) +} + +// Detail applies equality check predicate on the "detail" field. It's identical to DetailEQ. +func Detail(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldDetail, v)) +} + +// Operator applies equality check predicate on the "operator" field. It's identical to OperatorEQ. +func Operator(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldOperator, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldCreatedAt, v)) +} + +// OrderIDEQ applies the EQ predicate on the "order_id" field. +func OrderIDEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldOrderID, v)) +} + +// OrderIDNEQ applies the NEQ predicate on the "order_id" field. +func OrderIDNEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNEQ(FieldOrderID, v)) +} + +// OrderIDIn applies the In predicate on the "order_id" field. +func OrderIDIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldIn(FieldOrderID, vs...)) +} + +// OrderIDNotIn applies the NotIn predicate on the "order_id" field. +func OrderIDNotIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNotIn(FieldOrderID, vs...)) +} + +// OrderIDGT applies the GT predicate on the "order_id" field. +func OrderIDGT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGT(FieldOrderID, v)) +} + +// OrderIDGTE applies the GTE predicate on the "order_id" field. +func OrderIDGTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGTE(FieldOrderID, v)) +} + +// OrderIDLT applies the LT predicate on the "order_id" field. +func OrderIDLT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLT(FieldOrderID, v)) +} + +// OrderIDLTE applies the LTE predicate on the "order_id" field. +func OrderIDLTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLTE(FieldOrderID, v)) +} + +// OrderIDContains applies the Contains predicate on the "order_id" field. +func OrderIDContains(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContains(FieldOrderID, v)) +} + +// OrderIDHasPrefix applies the HasPrefix predicate on the "order_id" field. +func OrderIDHasPrefix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldOrderID, v)) +} + +// OrderIDHasSuffix applies the HasSuffix predicate on the "order_id" field. +func OrderIDHasSuffix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldOrderID, v)) +} + +// OrderIDEqualFold applies the EqualFold predicate on the "order_id" field. +func OrderIDEqualFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldOrderID, v)) +} + +// OrderIDContainsFold applies the ContainsFold predicate on the "order_id" field. +func OrderIDContainsFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldOrderID, v)) +} + +// ActionEQ applies the EQ predicate on the "action" field. +func ActionEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldAction, v)) +} + +// ActionNEQ applies the NEQ predicate on the "action" field. +func ActionNEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNEQ(FieldAction, v)) +} + +// ActionIn applies the In predicate on the "action" field. +func ActionIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldIn(FieldAction, vs...)) +} + +// ActionNotIn applies the NotIn predicate on the "action" field. +func ActionNotIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNotIn(FieldAction, vs...)) +} + +// ActionGT applies the GT predicate on the "action" field. +func ActionGT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGT(FieldAction, v)) +} + +// ActionGTE applies the GTE predicate on the "action" field. +func ActionGTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGTE(FieldAction, v)) +} + +// ActionLT applies the LT predicate on the "action" field. +func ActionLT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLT(FieldAction, v)) +} + +// ActionLTE applies the LTE predicate on the "action" field. +func ActionLTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLTE(FieldAction, v)) +} + +// ActionContains applies the Contains predicate on the "action" field. +func ActionContains(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContains(FieldAction, v)) +} + +// ActionHasPrefix applies the HasPrefix predicate on the "action" field. +func ActionHasPrefix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldAction, v)) +} + +// ActionHasSuffix applies the HasSuffix predicate on the "action" field. +func ActionHasSuffix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldAction, v)) +} + +// ActionEqualFold applies the EqualFold predicate on the "action" field. +func ActionEqualFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldAction, v)) +} + +// ActionContainsFold applies the ContainsFold predicate on the "action" field. +func ActionContainsFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldAction, v)) +} + +// DetailEQ applies the EQ predicate on the "detail" field. +func DetailEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldDetail, v)) +} + +// DetailNEQ applies the NEQ predicate on the "detail" field. +func DetailNEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNEQ(FieldDetail, v)) +} + +// DetailIn applies the In predicate on the "detail" field. +func DetailIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldIn(FieldDetail, vs...)) +} + +// DetailNotIn applies the NotIn predicate on the "detail" field. +func DetailNotIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNotIn(FieldDetail, vs...)) +} + +// DetailGT applies the GT predicate on the "detail" field. +func DetailGT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGT(FieldDetail, v)) +} + +// DetailGTE applies the GTE predicate on the "detail" field. +func DetailGTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGTE(FieldDetail, v)) +} + +// DetailLT applies the LT predicate on the "detail" field. +func DetailLT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLT(FieldDetail, v)) +} + +// DetailLTE applies the LTE predicate on the "detail" field. +func DetailLTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLTE(FieldDetail, v)) +} + +// DetailContains applies the Contains predicate on the "detail" field. +func DetailContains(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContains(FieldDetail, v)) +} + +// DetailHasPrefix applies the HasPrefix predicate on the "detail" field. +func DetailHasPrefix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldDetail, v)) +} + +// DetailHasSuffix applies the HasSuffix predicate on the "detail" field. +func DetailHasSuffix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldDetail, v)) +} + +// DetailEqualFold applies the EqualFold predicate on the "detail" field. +func DetailEqualFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldDetail, v)) +} + +// DetailContainsFold applies the ContainsFold predicate on the "detail" field. +func DetailContainsFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldDetail, v)) +} + +// OperatorEQ applies the EQ predicate on the "operator" field. +func OperatorEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldOperator, v)) +} + +// OperatorNEQ applies the NEQ predicate on the "operator" field. +func OperatorNEQ(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNEQ(FieldOperator, v)) +} + +// OperatorIn applies the In predicate on the "operator" field. +func OperatorIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldIn(FieldOperator, vs...)) +} + +// OperatorNotIn applies the NotIn predicate on the "operator" field. +func OperatorNotIn(vs ...string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNotIn(FieldOperator, vs...)) +} + +// OperatorGT applies the GT predicate on the "operator" field. +func OperatorGT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGT(FieldOperator, v)) +} + +// OperatorGTE applies the GTE predicate on the "operator" field. +func OperatorGTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGTE(FieldOperator, v)) +} + +// OperatorLT applies the LT predicate on the "operator" field. +func OperatorLT(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLT(FieldOperator, v)) +} + +// OperatorLTE applies the LTE predicate on the "operator" field. +func OperatorLTE(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLTE(FieldOperator, v)) +} + +// OperatorContains applies the Contains predicate on the "operator" field. +func OperatorContains(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContains(FieldOperator, v)) +} + +// OperatorHasPrefix applies the HasPrefix predicate on the "operator" field. +func OperatorHasPrefix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldOperator, v)) +} + +// OperatorHasSuffix applies the HasSuffix predicate on the "operator" field. +func OperatorHasSuffix(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldOperator, v)) +} + +// OperatorEqualFold applies the EqualFold predicate on the "operator" field. +func OperatorEqualFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldOperator, v)) +} + +// OperatorContainsFold applies the ContainsFold predicate on the "operator" field. +func OperatorContainsFold(v string) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldOperator, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.FieldLTE(FieldCreatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.PaymentAuditLog) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.PaymentAuditLog) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.PaymentAuditLog) predicate.PaymentAuditLog { + return predicate.PaymentAuditLog(sql.NotPredicates(p)) +} diff --git a/backend/ent/paymentauditlog_create.go b/backend/ent/paymentauditlog_create.go new file mode 100644 index 0000000000000000000000000000000000000000..1906aba110ba67ba068fa25e1e0b2c042572d1f8 --- /dev/null +++ b/backend/ent/paymentauditlog_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/paymentauditlog" +) + +// PaymentAuditLogCreate is the builder for creating a PaymentAuditLog entity. +type PaymentAuditLogCreate struct { + config + mutation *PaymentAuditLogMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetOrderID sets the "order_id" field. +func (_c *PaymentAuditLogCreate) SetOrderID(v string) *PaymentAuditLogCreate { + _c.mutation.SetOrderID(v) + return _c +} + +// SetAction sets the "action" field. +func (_c *PaymentAuditLogCreate) SetAction(v string) *PaymentAuditLogCreate { + _c.mutation.SetAction(v) + return _c +} + +// SetDetail sets the "detail" field. +func (_c *PaymentAuditLogCreate) SetDetail(v string) *PaymentAuditLogCreate { + _c.mutation.SetDetail(v) + return _c +} + +// SetNillableDetail sets the "detail" field if the given value is not nil. +func (_c *PaymentAuditLogCreate) SetNillableDetail(v *string) *PaymentAuditLogCreate { + if v != nil { + _c.SetDetail(*v) + } + return _c +} + +// SetOperator sets the "operator" field. +func (_c *PaymentAuditLogCreate) SetOperator(v string) *PaymentAuditLogCreate { + _c.mutation.SetOperator(v) + return _c +} + +// SetNillableOperator sets the "operator" field if the given value is not nil. +func (_c *PaymentAuditLogCreate) SetNillableOperator(v *string) *PaymentAuditLogCreate { + if v != nil { + _c.SetOperator(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *PaymentAuditLogCreate) SetCreatedAt(v time.Time) *PaymentAuditLogCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *PaymentAuditLogCreate) SetNillableCreatedAt(v *time.Time) *PaymentAuditLogCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// Mutation returns the PaymentAuditLogMutation object of the builder. +func (_c *PaymentAuditLogCreate) Mutation() *PaymentAuditLogMutation { + return _c.mutation +} + +// Save creates the PaymentAuditLog in the database. +func (_c *PaymentAuditLogCreate) Save(ctx context.Context) (*PaymentAuditLog, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *PaymentAuditLogCreate) SaveX(ctx context.Context) *PaymentAuditLog { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *PaymentAuditLogCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *PaymentAuditLogCreate) 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 *PaymentAuditLogCreate) defaults() { + if _, ok := _c.mutation.Detail(); !ok { + v := paymentauditlog.DefaultDetail + _c.mutation.SetDetail(v) + } + if _, ok := _c.mutation.Operator(); !ok { + v := paymentauditlog.DefaultOperator + _c.mutation.SetOperator(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := paymentauditlog.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *PaymentAuditLogCreate) check() error { + if _, ok := _c.mutation.OrderID(); !ok { + return &ValidationError{Name: "order_id", err: errors.New(`ent: missing required field "PaymentAuditLog.order_id"`)} + } + if v, ok := _c.mutation.OrderID(); ok { + if err := paymentauditlog.OrderIDValidator(v); err != nil { + return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)} + } + } + if _, ok := _c.mutation.Action(); !ok { + return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "PaymentAuditLog.action"`)} + } + if v, ok := _c.mutation.Action(); ok { + if err := paymentauditlog.ActionValidator(v); err != nil { + return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)} + } + } + if _, ok := _c.mutation.Detail(); !ok { + return &ValidationError{Name: "detail", err: errors.New(`ent: missing required field "PaymentAuditLog.detail"`)} + } + if _, ok := _c.mutation.Operator(); !ok { + return &ValidationError{Name: "operator", err: errors.New(`ent: missing required field "PaymentAuditLog.operator"`)} + } + if v, ok := _c.mutation.Operator(); ok { + if err := paymentauditlog.OperatorValidator(v); err != nil { + return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)} + } + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentAuditLog.created_at"`)} + } + return nil +} + +func (_c *PaymentAuditLogCreate) sqlSave(ctx context.Context) (*PaymentAuditLog, 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 *PaymentAuditLogCreate) createSpec() (*PaymentAuditLog, *sqlgraph.CreateSpec) { + var ( + _node = &PaymentAuditLog{config: _c.config} + _spec = sqlgraph.NewCreateSpec(paymentauditlog.Table, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if value, ok := _c.mutation.OrderID(); ok { + _spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value) + _node.OrderID = value + } + if value, ok := _c.mutation.Action(); ok { + _spec.SetField(paymentauditlog.FieldAction, field.TypeString, value) + _node.Action = value + } + if value, ok := _c.mutation.Detail(); ok { + _spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value) + _node.Detail = value + } + if value, ok := _c.mutation.Operator(); ok { + _spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value) + _node.Operator = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(paymentauditlog.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.PaymentAuditLog.Create(). +// SetOrderID(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.PaymentAuditLogUpsert) { +// SetOrderID(v+v). +// }). +// Exec(ctx) +func (_c *PaymentAuditLogCreate) OnConflict(opts ...sql.ConflictOption) *PaymentAuditLogUpsertOne { + _c.conflict = opts + return &PaymentAuditLogUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.PaymentAuditLog.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *PaymentAuditLogCreate) OnConflictColumns(columns ...string) *PaymentAuditLogUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &PaymentAuditLogUpsertOne{ + create: _c, + } +} + +type ( + // PaymentAuditLogUpsertOne is the builder for "upsert"-ing + // one PaymentAuditLog node. + PaymentAuditLogUpsertOne struct { + create *PaymentAuditLogCreate + } + + // PaymentAuditLogUpsert is the "OnConflict" setter. + PaymentAuditLogUpsert struct { + *sql.UpdateSet + } +) + +// SetOrderID sets the "order_id" field. +func (u *PaymentAuditLogUpsert) SetOrderID(v string) *PaymentAuditLogUpsert { + u.Set(paymentauditlog.FieldOrderID, v) + return u +} + +// UpdateOrderID sets the "order_id" field to the value that was provided on create. +func (u *PaymentAuditLogUpsert) UpdateOrderID() *PaymentAuditLogUpsert { + u.SetExcluded(paymentauditlog.FieldOrderID) + return u +} + +// SetAction sets the "action" field. +func (u *PaymentAuditLogUpsert) SetAction(v string) *PaymentAuditLogUpsert { + u.Set(paymentauditlog.FieldAction, v) + return u +} + +// UpdateAction sets the "action" field to the value that was provided on create. +func (u *PaymentAuditLogUpsert) UpdateAction() *PaymentAuditLogUpsert { + u.SetExcluded(paymentauditlog.FieldAction) + return u +} + +// SetDetail sets the "detail" field. +func (u *PaymentAuditLogUpsert) SetDetail(v string) *PaymentAuditLogUpsert { + u.Set(paymentauditlog.FieldDetail, v) + return u +} + +// UpdateDetail sets the "detail" field to the value that was provided on create. +func (u *PaymentAuditLogUpsert) UpdateDetail() *PaymentAuditLogUpsert { + u.SetExcluded(paymentauditlog.FieldDetail) + return u +} + +// SetOperator sets the "operator" field. +func (u *PaymentAuditLogUpsert) SetOperator(v string) *PaymentAuditLogUpsert { + u.Set(paymentauditlog.FieldOperator, v) + return u +} + +// UpdateOperator sets the "operator" field to the value that was provided on create. +func (u *PaymentAuditLogUpsert) UpdateOperator() *PaymentAuditLogUpsert { + u.SetExcluded(paymentauditlog.FieldOperator) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create. +// Using this option is equivalent to using: +// +// client.PaymentAuditLog.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *PaymentAuditLogUpsertOne) UpdateNewValues() *PaymentAuditLogUpsertOne { + 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(paymentauditlog.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.PaymentAuditLog.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *PaymentAuditLogUpsertOne) Ignore() *PaymentAuditLogUpsertOne { + 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 *PaymentAuditLogUpsertOne) DoNothing() *PaymentAuditLogUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the PaymentAuditLogCreate.OnConflict +// documentation for more info. +func (u *PaymentAuditLogUpsertOne) Update(set func(*PaymentAuditLogUpsert)) *PaymentAuditLogUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&PaymentAuditLogUpsert{UpdateSet: update}) + })) + return u +} + +// SetOrderID sets the "order_id" field. +func (u *PaymentAuditLogUpsertOne) SetOrderID(v string) *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetOrderID(v) + }) +} + +// UpdateOrderID sets the "order_id" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertOne) UpdateOrderID() *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateOrderID() + }) +} + +// SetAction sets the "action" field. +func (u *PaymentAuditLogUpsertOne) SetAction(v string) *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetAction(v) + }) +} + +// UpdateAction sets the "action" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertOne) UpdateAction() *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateAction() + }) +} + +// SetDetail sets the "detail" field. +func (u *PaymentAuditLogUpsertOne) SetDetail(v string) *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetDetail(v) + }) +} + +// UpdateDetail sets the "detail" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertOne) UpdateDetail() *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateDetail() + }) +} + +// SetOperator sets the "operator" field. +func (u *PaymentAuditLogUpsertOne) SetOperator(v string) *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetOperator(v) + }) +} + +// UpdateOperator sets the "operator" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertOne) UpdateOperator() *PaymentAuditLogUpsertOne { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateOperator() + }) +} + +// Exec executes the query. +func (u *PaymentAuditLogUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for PaymentAuditLogCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *PaymentAuditLogUpsertOne) 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 *PaymentAuditLogUpsertOne) 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 *PaymentAuditLogUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// PaymentAuditLogCreateBulk is the builder for creating many PaymentAuditLog entities in bulk. +type PaymentAuditLogCreateBulk struct { + config + err error + builders []*PaymentAuditLogCreate + conflict []sql.ConflictOption +} + +// Save creates the PaymentAuditLog entities in the database. +func (_c *PaymentAuditLogCreateBulk) Save(ctx context.Context) ([]*PaymentAuditLog, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*PaymentAuditLog, 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.(*PaymentAuditLogMutation) + 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 *PaymentAuditLogCreateBulk) SaveX(ctx context.Context) []*PaymentAuditLog { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *PaymentAuditLogCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *PaymentAuditLogCreateBulk) 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.PaymentAuditLog.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.PaymentAuditLogUpsert) { +// SetOrderID(v+v). +// }). +// Exec(ctx) +func (_c *PaymentAuditLogCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentAuditLogUpsertBulk { + _c.conflict = opts + return &PaymentAuditLogUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.PaymentAuditLog.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *PaymentAuditLogCreateBulk) OnConflictColumns(columns ...string) *PaymentAuditLogUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &PaymentAuditLogUpsertBulk{ + create: _c, + } +} + +// PaymentAuditLogUpsertBulk is the builder for "upsert"-ing +// a bulk of PaymentAuditLog nodes. +type PaymentAuditLogUpsertBulk struct { + create *PaymentAuditLogCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.PaymentAuditLog.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *PaymentAuditLogUpsertBulk) UpdateNewValues() *PaymentAuditLogUpsertBulk { + 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(paymentauditlog.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.PaymentAuditLog.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *PaymentAuditLogUpsertBulk) Ignore() *PaymentAuditLogUpsertBulk { + 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 *PaymentAuditLogUpsertBulk) DoNothing() *PaymentAuditLogUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the PaymentAuditLogCreateBulk.OnConflict +// documentation for more info. +func (u *PaymentAuditLogUpsertBulk) Update(set func(*PaymentAuditLogUpsert)) *PaymentAuditLogUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&PaymentAuditLogUpsert{UpdateSet: update}) + })) + return u +} + +// SetOrderID sets the "order_id" field. +func (u *PaymentAuditLogUpsertBulk) SetOrderID(v string) *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetOrderID(v) + }) +} + +// UpdateOrderID sets the "order_id" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertBulk) UpdateOrderID() *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateOrderID() + }) +} + +// SetAction sets the "action" field. +func (u *PaymentAuditLogUpsertBulk) SetAction(v string) *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetAction(v) + }) +} + +// UpdateAction sets the "action" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertBulk) UpdateAction() *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateAction() + }) +} + +// SetDetail sets the "detail" field. +func (u *PaymentAuditLogUpsertBulk) SetDetail(v string) *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetDetail(v) + }) +} + +// UpdateDetail sets the "detail" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertBulk) UpdateDetail() *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateDetail() + }) +} + +// SetOperator sets the "operator" field. +func (u *PaymentAuditLogUpsertBulk) SetOperator(v string) *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.SetOperator(v) + }) +} + +// UpdateOperator sets the "operator" field to the value that was provided on create. +func (u *PaymentAuditLogUpsertBulk) UpdateOperator() *PaymentAuditLogUpsertBulk { + return u.Update(func(s *PaymentAuditLogUpsert) { + s.UpdateOperator() + }) +} + +// Exec executes the query. +func (u *PaymentAuditLogUpsertBulk) 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 PaymentAuditLogCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for PaymentAuditLogCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *PaymentAuditLogUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/paymentauditlog_delete.go b/backend/ent/paymentauditlog_delete.go new file mode 100644 index 0000000000000000000000000000000000000000..ca22d8db37e450e7ccd510a4c0026b17c401dd8f --- /dev/null +++ b/backend/ent/paymentauditlog_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/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentAuditLogDelete is the builder for deleting a PaymentAuditLog entity. +type PaymentAuditLogDelete struct { + config + hooks []Hook + mutation *PaymentAuditLogMutation +} + +// Where appends a list predicates to the PaymentAuditLogDelete builder. +func (_d *PaymentAuditLogDelete) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *PaymentAuditLogDelete) 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 *PaymentAuditLogDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *PaymentAuditLogDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(paymentauditlog.Table, sqlgraph.NewFieldSpec(paymentauditlog.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 +} + +// PaymentAuditLogDeleteOne is the builder for deleting a single PaymentAuditLog entity. +type PaymentAuditLogDeleteOne struct { + _d *PaymentAuditLogDelete +} + +// Where appends a list predicates to the PaymentAuditLogDelete builder. +func (_d *PaymentAuditLogDeleteOne) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *PaymentAuditLogDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{paymentauditlog.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *PaymentAuditLogDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/paymentauditlog_query.go b/backend/ent/paymentauditlog_query.go new file mode 100644 index 0000000000000000000000000000000000000000..7a4e9115e7f5820ae81f26ea9838964b8115f20d --- /dev/null +++ b/backend/ent/paymentauditlog_query.go @@ -0,0 +1,564 @@ +// 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/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentAuditLogQuery is the builder for querying PaymentAuditLog entities. +type PaymentAuditLogQuery struct { + config + ctx *QueryContext + order []paymentauditlog.OrderOption + inters []Interceptor + predicates []predicate.PaymentAuditLog + 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 PaymentAuditLogQuery builder. +func (_q *PaymentAuditLogQuery) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *PaymentAuditLogQuery) Limit(limit int) *PaymentAuditLogQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *PaymentAuditLogQuery) Offset(offset int) *PaymentAuditLogQuery { + _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 *PaymentAuditLogQuery) Unique(unique bool) *PaymentAuditLogQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *PaymentAuditLogQuery) Order(o ...paymentauditlog.OrderOption) *PaymentAuditLogQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first PaymentAuditLog entity from the query. +// Returns a *NotFoundError when no PaymentAuditLog was found. +func (_q *PaymentAuditLogQuery) First(ctx context.Context) (*PaymentAuditLog, 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{paymentauditlog.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) FirstX(ctx context.Context) *PaymentAuditLog { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first PaymentAuditLog ID from the query. +// Returns a *NotFoundError when no PaymentAuditLog ID was found. +func (_q *PaymentAuditLogQuery) 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{paymentauditlog.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single PaymentAuditLog entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one PaymentAuditLog entity is found. +// Returns a *NotFoundError when no PaymentAuditLog entities are found. +func (_q *PaymentAuditLogQuery) Only(ctx context.Context) (*PaymentAuditLog, 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{paymentauditlog.Label} + default: + return nil, &NotSingularError{paymentauditlog.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) OnlyX(ctx context.Context) *PaymentAuditLog { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only PaymentAuditLog ID in the query. +// Returns a *NotSingularError when more than one PaymentAuditLog ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *PaymentAuditLogQuery) 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{paymentauditlog.Label} + default: + err = &NotSingularError{paymentauditlog.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) 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 PaymentAuditLogs. +func (_q *PaymentAuditLogQuery) All(ctx context.Context) ([]*PaymentAuditLog, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*PaymentAuditLog, *PaymentAuditLogQuery]() + return withInterceptors[[]*PaymentAuditLog](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) AllX(ctx context.Context) []*PaymentAuditLog { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of PaymentAuditLog IDs. +func (_q *PaymentAuditLogQuery) 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(paymentauditlog.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) 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[*PaymentAuditLogQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the PaymentAuditLogQuery 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 *PaymentAuditLogQuery) Clone() *PaymentAuditLogQuery { + if _q == nil { + return nil + } + return &PaymentAuditLogQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]paymentauditlog.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.PaymentAuditLog{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// 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 { +// OrderID string `json:"order_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.PaymentAuditLog.Query(). +// GroupBy(paymentauditlog.FieldOrderID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *PaymentAuditLogQuery) GroupBy(field string, fields ...string) *PaymentAuditLogGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &PaymentAuditLogGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = paymentauditlog.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 { +// OrderID string `json:"order_id,omitempty"` +// } +// +// client.PaymentAuditLog.Query(). +// Select(paymentauditlog.FieldOrderID). +// Scan(ctx, &v) +func (_q *PaymentAuditLogQuery) Select(fields ...string) *PaymentAuditLogSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &PaymentAuditLogSelect{PaymentAuditLogQuery: _q} + sbuild.label = paymentauditlog.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a PaymentAuditLogSelect configured with the given aggregations. +func (_q *PaymentAuditLogQuery) Aggregate(fns ...AggregateFunc) *PaymentAuditLogSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *PaymentAuditLogQuery) 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 !paymentauditlog.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 *PaymentAuditLogQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentAuditLog, error) { + var ( + nodes = []*PaymentAuditLog{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*PaymentAuditLog).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &PaymentAuditLog{config: _q.config} + 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) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *PaymentAuditLogQuery) 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 *PaymentAuditLogQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.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, paymentauditlog.FieldID) + for i := range fields { + if fields[i] != paymentauditlog.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 *PaymentAuditLogQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(paymentauditlog.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = paymentauditlog.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 *PaymentAuditLogQuery) ForUpdate(opts ...sql.LockOption) *PaymentAuditLogQuery { + 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 *PaymentAuditLogQuery) ForShare(opts ...sql.LockOption) *PaymentAuditLogQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// PaymentAuditLogGroupBy is the group-by builder for PaymentAuditLog entities. +type PaymentAuditLogGroupBy struct { + selector + build *PaymentAuditLogQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *PaymentAuditLogGroupBy) Aggregate(fns ...AggregateFunc) *PaymentAuditLogGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *PaymentAuditLogGroupBy) 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[*PaymentAuditLogQuery, *PaymentAuditLogGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *PaymentAuditLogGroupBy) sqlScan(ctx context.Context, root *PaymentAuditLogQuery, 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) +} + +// PaymentAuditLogSelect is the builder for selecting fields of PaymentAuditLog entities. +type PaymentAuditLogSelect struct { + *PaymentAuditLogQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *PaymentAuditLogSelect) Aggregate(fns ...AggregateFunc) *PaymentAuditLogSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *PaymentAuditLogSelect) 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[*PaymentAuditLogQuery, *PaymentAuditLogSelect](ctx, _s.PaymentAuditLogQuery, _s, _s.inters, v) +} + +func (_s *PaymentAuditLogSelect) sqlScan(ctx context.Context, root *PaymentAuditLogQuery, 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/paymentauditlog_update.go b/backend/ent/paymentauditlog_update.go new file mode 100644 index 0000000000000000000000000000000000000000..52b4afe794437008afc840096a7cc6080f485a90 --- /dev/null +++ b/backend/ent/paymentauditlog_update.go @@ -0,0 +1,357 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentAuditLogUpdate is the builder for updating PaymentAuditLog entities. +type PaymentAuditLogUpdate struct { + config + hooks []Hook + mutation *PaymentAuditLogMutation +} + +// Where appends a list predicates to the PaymentAuditLogUpdate builder. +func (_u *PaymentAuditLogUpdate) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetOrderID sets the "order_id" field. +func (_u *PaymentAuditLogUpdate) SetOrderID(v string) *PaymentAuditLogUpdate { + _u.mutation.SetOrderID(v) + return _u +} + +// SetNillableOrderID sets the "order_id" field if the given value is not nil. +func (_u *PaymentAuditLogUpdate) SetNillableOrderID(v *string) *PaymentAuditLogUpdate { + if v != nil { + _u.SetOrderID(*v) + } + return _u +} + +// SetAction sets the "action" field. +func (_u *PaymentAuditLogUpdate) SetAction(v string) *PaymentAuditLogUpdate { + _u.mutation.SetAction(v) + return _u +} + +// SetNillableAction sets the "action" field if the given value is not nil. +func (_u *PaymentAuditLogUpdate) SetNillableAction(v *string) *PaymentAuditLogUpdate { + if v != nil { + _u.SetAction(*v) + } + return _u +} + +// SetDetail sets the "detail" field. +func (_u *PaymentAuditLogUpdate) SetDetail(v string) *PaymentAuditLogUpdate { + _u.mutation.SetDetail(v) + return _u +} + +// SetNillableDetail sets the "detail" field if the given value is not nil. +func (_u *PaymentAuditLogUpdate) SetNillableDetail(v *string) *PaymentAuditLogUpdate { + if v != nil { + _u.SetDetail(*v) + } + return _u +} + +// SetOperator sets the "operator" field. +func (_u *PaymentAuditLogUpdate) SetOperator(v string) *PaymentAuditLogUpdate { + _u.mutation.SetOperator(v) + return _u +} + +// SetNillableOperator sets the "operator" field if the given value is not nil. +func (_u *PaymentAuditLogUpdate) SetNillableOperator(v *string) *PaymentAuditLogUpdate { + if v != nil { + _u.SetOperator(*v) + } + return _u +} + +// Mutation returns the PaymentAuditLogMutation object of the builder. +func (_u *PaymentAuditLogUpdate) Mutation() *PaymentAuditLogMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *PaymentAuditLogUpdate) 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 *PaymentAuditLogUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *PaymentAuditLogUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *PaymentAuditLogUpdate) 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 *PaymentAuditLogUpdate) check() error { + if v, ok := _u.mutation.OrderID(); ok { + if err := paymentauditlog.OrderIDValidator(v); err != nil { + return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)} + } + } + if v, ok := _u.mutation.Action(); ok { + if err := paymentauditlog.ActionValidator(v); err != nil { + return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)} + } + } + if v, ok := _u.mutation.Operator(); ok { + if err := paymentauditlog.OperatorValidator(v); err != nil { + return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)} + } + } + return nil +} + +func (_u *PaymentAuditLogUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.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.OrderID(); ok { + _spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value) + } + if value, ok := _u.mutation.Action(); ok { + _spec.SetField(paymentauditlog.FieldAction, field.TypeString, value) + } + if value, ok := _u.mutation.Detail(); ok { + _spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value) + } + if value, ok := _u.mutation.Operator(); ok { + _spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{paymentauditlog.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// PaymentAuditLogUpdateOne is the builder for updating a single PaymentAuditLog entity. +type PaymentAuditLogUpdateOne struct { + config + fields []string + hooks []Hook + mutation *PaymentAuditLogMutation +} + +// SetOrderID sets the "order_id" field. +func (_u *PaymentAuditLogUpdateOne) SetOrderID(v string) *PaymentAuditLogUpdateOne { + _u.mutation.SetOrderID(v) + return _u +} + +// SetNillableOrderID sets the "order_id" field if the given value is not nil. +func (_u *PaymentAuditLogUpdateOne) SetNillableOrderID(v *string) *PaymentAuditLogUpdateOne { + if v != nil { + _u.SetOrderID(*v) + } + return _u +} + +// SetAction sets the "action" field. +func (_u *PaymentAuditLogUpdateOne) SetAction(v string) *PaymentAuditLogUpdateOne { + _u.mutation.SetAction(v) + return _u +} + +// SetNillableAction sets the "action" field if the given value is not nil. +func (_u *PaymentAuditLogUpdateOne) SetNillableAction(v *string) *PaymentAuditLogUpdateOne { + if v != nil { + _u.SetAction(*v) + } + return _u +} + +// SetDetail sets the "detail" field. +func (_u *PaymentAuditLogUpdateOne) SetDetail(v string) *PaymentAuditLogUpdateOne { + _u.mutation.SetDetail(v) + return _u +} + +// SetNillableDetail sets the "detail" field if the given value is not nil. +func (_u *PaymentAuditLogUpdateOne) SetNillableDetail(v *string) *PaymentAuditLogUpdateOne { + if v != nil { + _u.SetDetail(*v) + } + return _u +} + +// SetOperator sets the "operator" field. +func (_u *PaymentAuditLogUpdateOne) SetOperator(v string) *PaymentAuditLogUpdateOne { + _u.mutation.SetOperator(v) + return _u +} + +// SetNillableOperator sets the "operator" field if the given value is not nil. +func (_u *PaymentAuditLogUpdateOne) SetNillableOperator(v *string) *PaymentAuditLogUpdateOne { + if v != nil { + _u.SetOperator(*v) + } + return _u +} + +// Mutation returns the PaymentAuditLogMutation object of the builder. +func (_u *PaymentAuditLogUpdateOne) Mutation() *PaymentAuditLogMutation { + return _u.mutation +} + +// Where appends a list predicates to the PaymentAuditLogUpdate builder. +func (_u *PaymentAuditLogUpdateOne) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogUpdateOne { + _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 *PaymentAuditLogUpdateOne) Select(field string, fields ...string) *PaymentAuditLogUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated PaymentAuditLog entity. +func (_u *PaymentAuditLogUpdateOne) Save(ctx context.Context) (*PaymentAuditLog, error) { + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *PaymentAuditLogUpdateOne) SaveX(ctx context.Context) *PaymentAuditLog { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *PaymentAuditLogUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *PaymentAuditLogUpdateOne) 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 *PaymentAuditLogUpdateOne) check() error { + if v, ok := _u.mutation.OrderID(); ok { + if err := paymentauditlog.OrderIDValidator(v); err != nil { + return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)} + } + } + if v, ok := _u.mutation.Action(); ok { + if err := paymentauditlog.ActionValidator(v); err != nil { + return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)} + } + } + if v, ok := _u.mutation.Operator(); ok { + if err := paymentauditlog.OperatorValidator(v); err != nil { + return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)} + } + } + return nil +} + +func (_u *PaymentAuditLogUpdateOne) sqlSave(ctx context.Context) (_node *PaymentAuditLog, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentAuditLog.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, paymentauditlog.FieldID) + for _, f := range fields { + if !paymentauditlog.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != paymentauditlog.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.OrderID(); ok { + _spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value) + } + if value, ok := _u.mutation.Action(); ok { + _spec.SetField(paymentauditlog.FieldAction, field.TypeString, value) + } + if value, ok := _u.mutation.Detail(); ok { + _spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value) + } + if value, ok := _u.mutation.Operator(); ok { + _spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value) + } + _node = &PaymentAuditLog{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{paymentauditlog.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/paymentorder.go b/backend/ent/paymentorder.go new file mode 100644 index 0000000000000000000000000000000000000000..6ea3e70981d1884751b7512e541f53c057c1c206 --- /dev/null +++ b/backend/ent/paymentorder.go @@ -0,0 +1,589 @@ +// 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/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/user" +) + +// PaymentOrder is the model entity for the PaymentOrder schema. +type PaymentOrder struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // UserID holds the value of the "user_id" field. + UserID int64 `json:"user_id,omitempty"` + // UserEmail holds the value of the "user_email" field. + UserEmail string `json:"user_email,omitempty"` + // UserName holds the value of the "user_name" field. + UserName string `json:"user_name,omitempty"` + // UserNotes holds the value of the "user_notes" field. + UserNotes *string `json:"user_notes,omitempty"` + // Amount holds the value of the "amount" field. + Amount float64 `json:"amount,omitempty"` + // PayAmount holds the value of the "pay_amount" field. + PayAmount float64 `json:"pay_amount,omitempty"` + // FeeRate holds the value of the "fee_rate" field. + FeeRate float64 `json:"fee_rate,omitempty"` + // RechargeCode holds the value of the "recharge_code" field. + RechargeCode string `json:"recharge_code,omitempty"` + // OutTradeNo holds the value of the "out_trade_no" field. + OutTradeNo string `json:"out_trade_no,omitempty"` + // PaymentType holds the value of the "payment_type" field. + PaymentType string `json:"payment_type,omitempty"` + // PaymentTradeNo holds the value of the "payment_trade_no" field. + PaymentTradeNo string `json:"payment_trade_no,omitempty"` + // PayURL holds the value of the "pay_url" field. + PayURL *string `json:"pay_url,omitempty"` + // QrCode holds the value of the "qr_code" field. + QrCode *string `json:"qr_code,omitempty"` + // QrCodeImg holds the value of the "qr_code_img" field. + QrCodeImg *string `json:"qr_code_img,omitempty"` + // OrderType holds the value of the "order_type" field. + OrderType string `json:"order_type,omitempty"` + // PlanID holds the value of the "plan_id" field. + PlanID *int64 `json:"plan_id,omitempty"` + // SubscriptionGroupID holds the value of the "subscription_group_id" field. + SubscriptionGroupID *int64 `json:"subscription_group_id,omitempty"` + // SubscriptionDays holds the value of the "subscription_days" field. + SubscriptionDays *int `json:"subscription_days,omitempty"` + // ProviderInstanceID holds the value of the "provider_instance_id" field. + ProviderInstanceID *string `json:"provider_instance_id,omitempty"` + // Status holds the value of the "status" field. + Status string `json:"status,omitempty"` + // RefundAmount holds the value of the "refund_amount" field. + RefundAmount float64 `json:"refund_amount,omitempty"` + // RefundReason holds the value of the "refund_reason" field. + RefundReason *string `json:"refund_reason,omitempty"` + // RefundAt holds the value of the "refund_at" field. + RefundAt *time.Time `json:"refund_at,omitempty"` + // ForceRefund holds the value of the "force_refund" field. + ForceRefund bool `json:"force_refund,omitempty"` + // RefundRequestedAt holds the value of the "refund_requested_at" field. + RefundRequestedAt *time.Time `json:"refund_requested_at,omitempty"` + // RefundRequestReason holds the value of the "refund_request_reason" field. + RefundRequestReason *string `json:"refund_request_reason,omitempty"` + // RefundRequestedBy holds the value of the "refund_requested_by" field. + RefundRequestedBy *string `json:"refund_requested_by,omitempty"` + // ExpiresAt holds the value of the "expires_at" field. + ExpiresAt time.Time `json:"expires_at,omitempty"` + // PaidAt holds the value of the "paid_at" field. + PaidAt *time.Time `json:"paid_at,omitempty"` + // CompletedAt holds the value of the "completed_at" field. + CompletedAt *time.Time `json:"completed_at,omitempty"` + // FailedAt holds the value of the "failed_at" field. + FailedAt *time.Time `json:"failed_at,omitempty"` + // FailedReason holds the value of the "failed_reason" field. + FailedReason *string `json:"failed_reason,omitempty"` + // ClientIP holds the value of the "client_ip" field. + ClientIP string `json:"client_ip,omitempty"` + // SrcHost holds the value of the "src_host" field. + SrcHost string `json:"src_host,omitempty"` + // SrcURL holds the value of the "src_url" field. + SrcURL *string `json:"src_url,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 PaymentOrderQuery when eager-loading is set. + Edges PaymentOrderEdges `json:"edges"` + selectValues sql.SelectValues +} + +// PaymentOrderEdges holds the relations/edges for other nodes in the graph. +type PaymentOrderEdges struct { + // 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 [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e PaymentOrderEdges) UserOrErr() (*User, error) { + if e.User != nil { + return e.User, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: user.Label} + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*PaymentOrder) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case paymentorder.FieldForceRefund: + values[i] = new(sql.NullBool) + case paymentorder.FieldAmount, paymentorder.FieldPayAmount, paymentorder.FieldFeeRate, paymentorder.FieldRefundAmount: + values[i] = new(sql.NullFloat64) + case paymentorder.FieldID, paymentorder.FieldUserID, paymentorder.FieldPlanID, paymentorder.FieldSubscriptionGroupID, paymentorder.FieldSubscriptionDays: + values[i] = new(sql.NullInt64) + case paymentorder.FieldUserEmail, paymentorder.FieldUserName, paymentorder.FieldUserNotes, paymentorder.FieldRechargeCode, paymentorder.FieldOutTradeNo, paymentorder.FieldPaymentType, paymentorder.FieldPaymentTradeNo, paymentorder.FieldPayURL, paymentorder.FieldQrCode, paymentorder.FieldQrCodeImg, paymentorder.FieldOrderType, paymentorder.FieldProviderInstanceID, paymentorder.FieldStatus, paymentorder.FieldRefundReason, paymentorder.FieldRefundRequestReason, paymentorder.FieldRefundRequestedBy, paymentorder.FieldFailedReason, paymentorder.FieldClientIP, paymentorder.FieldSrcHost, paymentorder.FieldSrcURL: + values[i] = new(sql.NullString) + case paymentorder.FieldRefundAt, paymentorder.FieldRefundRequestedAt, paymentorder.FieldExpiresAt, paymentorder.FieldPaidAt, paymentorder.FieldCompletedAt, paymentorder.FieldFailedAt, paymentorder.FieldCreatedAt, paymentorder.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 PaymentOrder fields. +func (_m *PaymentOrder) 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 paymentorder.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 paymentorder.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 paymentorder.FieldUserEmail: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_email", values[i]) + } else if value.Valid { + _m.UserEmail = value.String + } + case paymentorder.FieldUserName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_name", values[i]) + } else if value.Valid { + _m.UserName = value.String + } + case paymentorder.FieldUserNotes: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field user_notes", values[i]) + } else if value.Valid { + _m.UserNotes = new(string) + *_m.UserNotes = value.String + } + case paymentorder.FieldAmount: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field amount", values[i]) + } else if value.Valid { + _m.Amount = value.Float64 + } + case paymentorder.FieldPayAmount: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field pay_amount", values[i]) + } else if value.Valid { + _m.PayAmount = value.Float64 + } + case paymentorder.FieldFeeRate: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field fee_rate", values[i]) + } else if value.Valid { + _m.FeeRate = value.Float64 + } + case paymentorder.FieldRechargeCode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field recharge_code", values[i]) + } else if value.Valid { + _m.RechargeCode = value.String + } + case paymentorder.FieldOutTradeNo: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field out_trade_no", values[i]) + } else if value.Valid { + _m.OutTradeNo = value.String + } + case paymentorder.FieldPaymentType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field payment_type", values[i]) + } else if value.Valid { + _m.PaymentType = value.String + } + case paymentorder.FieldPaymentTradeNo: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field payment_trade_no", values[i]) + } else if value.Valid { + _m.PaymentTradeNo = value.String + } + case paymentorder.FieldPayURL: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field pay_url", values[i]) + } else if value.Valid { + _m.PayURL = new(string) + *_m.PayURL = value.String + } + case paymentorder.FieldQrCode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field qr_code", values[i]) + } else if value.Valid { + _m.QrCode = new(string) + *_m.QrCode = value.String + } + case paymentorder.FieldQrCodeImg: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field qr_code_img", values[i]) + } else if value.Valid { + _m.QrCodeImg = new(string) + *_m.QrCodeImg = value.String + } + case paymentorder.FieldOrderType: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field order_type", values[i]) + } else if value.Valid { + _m.OrderType = value.String + } + case paymentorder.FieldPlanID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field plan_id", values[i]) + } else if value.Valid { + _m.PlanID = new(int64) + *_m.PlanID = value.Int64 + } + case paymentorder.FieldSubscriptionGroupID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field subscription_group_id", values[i]) + } else if value.Valid { + _m.SubscriptionGroupID = new(int64) + *_m.SubscriptionGroupID = value.Int64 + } + case paymentorder.FieldSubscriptionDays: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field subscription_days", values[i]) + } else if value.Valid { + _m.SubscriptionDays = new(int) + *_m.SubscriptionDays = int(value.Int64) + } + case paymentorder.FieldProviderInstanceID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field provider_instance_id", values[i]) + } else if value.Valid { + _m.ProviderInstanceID = new(string) + *_m.ProviderInstanceID = value.String + } + case paymentorder.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 paymentorder.FieldRefundAmount: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field refund_amount", values[i]) + } else if value.Valid { + _m.RefundAmount = value.Float64 + } + case paymentorder.FieldRefundReason: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field refund_reason", values[i]) + } else if value.Valid { + _m.RefundReason = new(string) + *_m.RefundReason = value.String + } + case paymentorder.FieldRefundAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field refund_at", values[i]) + } else if value.Valid { + _m.RefundAt = new(time.Time) + *_m.RefundAt = value.Time + } + case paymentorder.FieldForceRefund: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field force_refund", values[i]) + } else if value.Valid { + _m.ForceRefund = value.Bool + } + case paymentorder.FieldRefundRequestedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field refund_requested_at", values[i]) + } else if value.Valid { + _m.RefundRequestedAt = new(time.Time) + *_m.RefundRequestedAt = value.Time + } + case paymentorder.FieldRefundRequestReason: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field refund_request_reason", values[i]) + } else if value.Valid { + _m.RefundRequestReason = new(string) + *_m.RefundRequestReason = value.String + } + case paymentorder.FieldRefundRequestedBy: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field refund_requested_by", values[i]) + } else if value.Valid { + _m.RefundRequestedBy = new(string) + *_m.RefundRequestedBy = value.String + } + case paymentorder.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 = value.Time + } + case paymentorder.FieldPaidAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field paid_at", values[i]) + } else if value.Valid { + _m.PaidAt = new(time.Time) + *_m.PaidAt = value.Time + } + case paymentorder.FieldCompletedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field completed_at", values[i]) + } else if value.Valid { + _m.CompletedAt = new(time.Time) + *_m.CompletedAt = value.Time + } + case paymentorder.FieldFailedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field failed_at", values[i]) + } else if value.Valid { + _m.FailedAt = new(time.Time) + *_m.FailedAt = value.Time + } + case paymentorder.FieldFailedReason: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field failed_reason", values[i]) + } else if value.Valid { + _m.FailedReason = new(string) + *_m.FailedReason = value.String + } + case paymentorder.FieldClientIP: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field client_ip", values[i]) + } else if value.Valid { + _m.ClientIP = value.String + } + case paymentorder.FieldSrcHost: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field src_host", values[i]) + } else if value.Valid { + _m.SrcHost = value.String + } + case paymentorder.FieldSrcURL: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field src_url", values[i]) + } else if value.Valid { + _m.SrcURL = new(string) + *_m.SrcURL = value.String + } + case paymentorder.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 paymentorder.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 PaymentOrder. +// This includes values selected through modifiers, order, etc. +func (_m *PaymentOrder) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the PaymentOrder entity. +func (_m *PaymentOrder) QueryUser() *UserQuery { + return NewPaymentOrderClient(_m.config).QueryUser(_m) +} + +// Update returns a builder for updating this PaymentOrder. +// Note that you need to call PaymentOrder.Unwrap() before calling this method if this PaymentOrder +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *PaymentOrder) Update() *PaymentOrderUpdateOne { + return NewPaymentOrderClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the PaymentOrder 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 *PaymentOrder) Unwrap() *PaymentOrder { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: PaymentOrder is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *PaymentOrder) String() string { + var builder strings.Builder + builder.WriteString("PaymentOrder(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("user_id=") + builder.WriteString(fmt.Sprintf("%v", _m.UserID)) + builder.WriteString(", ") + builder.WriteString("user_email=") + builder.WriteString(_m.UserEmail) + builder.WriteString(", ") + builder.WriteString("user_name=") + builder.WriteString(_m.UserName) + builder.WriteString(", ") + if v := _m.UserNotes; v != nil { + builder.WriteString("user_notes=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("amount=") + builder.WriteString(fmt.Sprintf("%v", _m.Amount)) + builder.WriteString(", ") + builder.WriteString("pay_amount=") + builder.WriteString(fmt.Sprintf("%v", _m.PayAmount)) + builder.WriteString(", ") + builder.WriteString("fee_rate=") + builder.WriteString(fmt.Sprintf("%v", _m.FeeRate)) + builder.WriteString(", ") + builder.WriteString("recharge_code=") + builder.WriteString(_m.RechargeCode) + builder.WriteString(", ") + builder.WriteString("out_trade_no=") + builder.WriteString(_m.OutTradeNo) + builder.WriteString(", ") + builder.WriteString("payment_type=") + builder.WriteString(_m.PaymentType) + builder.WriteString(", ") + builder.WriteString("payment_trade_no=") + builder.WriteString(_m.PaymentTradeNo) + builder.WriteString(", ") + if v := _m.PayURL; v != nil { + builder.WriteString("pay_url=") + builder.WriteString(*v) + } + builder.WriteString(", ") + if v := _m.QrCode; v != nil { + builder.WriteString("qr_code=") + builder.WriteString(*v) + } + builder.WriteString(", ") + if v := _m.QrCodeImg; v != nil { + builder.WriteString("qr_code_img=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("order_type=") + builder.WriteString(_m.OrderType) + builder.WriteString(", ") + if v := _m.PlanID; v != nil { + builder.WriteString("plan_id=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.SubscriptionGroupID; v != nil { + builder.WriteString("subscription_group_id=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.SubscriptionDays; v != nil { + builder.WriteString("subscription_days=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + if v := _m.ProviderInstanceID; v != nil { + builder.WriteString("provider_instance_id=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("status=") + builder.WriteString(_m.Status) + builder.WriteString(", ") + builder.WriteString("refund_amount=") + builder.WriteString(fmt.Sprintf("%v", _m.RefundAmount)) + builder.WriteString(", ") + if v := _m.RefundReason; v != nil { + builder.WriteString("refund_reason=") + builder.WriteString(*v) + } + builder.WriteString(", ") + if v := _m.RefundAt; v != nil { + builder.WriteString("refund_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + builder.WriteString("force_refund=") + builder.WriteString(fmt.Sprintf("%v", _m.ForceRefund)) + builder.WriteString(", ") + if v := _m.RefundRequestedAt; v != nil { + builder.WriteString("refund_requested_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + if v := _m.RefundRequestReason; v != nil { + builder.WriteString("refund_request_reason=") + builder.WriteString(*v) + } + builder.WriteString(", ") + if v := _m.RefundRequestedBy; v != nil { + builder.WriteString("refund_requested_by=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("expires_at=") + builder.WriteString(_m.ExpiresAt.Format(time.ANSIC)) + builder.WriteString(", ") + if v := _m.PaidAt; v != nil { + builder.WriteString("paid_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + if v := _m.CompletedAt; v != nil { + builder.WriteString("completed_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + if v := _m.FailedAt; v != nil { + builder.WriteString("failed_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + if v := _m.FailedReason; v != nil { + builder.WriteString("failed_reason=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("client_ip=") + builder.WriteString(_m.ClientIP) + builder.WriteString(", ") + builder.WriteString("src_host=") + builder.WriteString(_m.SrcHost) + builder.WriteString(", ") + if v := _m.SrcURL; v != nil { + builder.WriteString("src_url=") + 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() +} + +// PaymentOrders is a parsable slice of PaymentOrder. +type PaymentOrders []*PaymentOrder diff --git a/backend/ent/paymentorder/paymentorder.go b/backend/ent/paymentorder/paymentorder.go new file mode 100644 index 0000000000000000000000000000000000000000..4467b2b635896402c3254245eff2fec0d8fb4136 --- /dev/null +++ b/backend/ent/paymentorder/paymentorder.go @@ -0,0 +1,406 @@ +// Code generated by ent, DO NOT EDIT. + +package paymentorder + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the paymentorder type in the database. + Label = "payment_order" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldUserEmail holds the string denoting the user_email field in the database. + FieldUserEmail = "user_email" + // FieldUserName holds the string denoting the user_name field in the database. + FieldUserName = "user_name" + // FieldUserNotes holds the string denoting the user_notes field in the database. + FieldUserNotes = "user_notes" + // FieldAmount holds the string denoting the amount field in the database. + FieldAmount = "amount" + // FieldPayAmount holds the string denoting the pay_amount field in the database. + FieldPayAmount = "pay_amount" + // FieldFeeRate holds the string denoting the fee_rate field in the database. + FieldFeeRate = "fee_rate" + // FieldRechargeCode holds the string denoting the recharge_code field in the database. + FieldRechargeCode = "recharge_code" + // FieldOutTradeNo holds the string denoting the out_trade_no field in the database. + FieldOutTradeNo = "out_trade_no" + // FieldPaymentType holds the string denoting the payment_type field in the database. + FieldPaymentType = "payment_type" + // FieldPaymentTradeNo holds the string denoting the payment_trade_no field in the database. + FieldPaymentTradeNo = "payment_trade_no" + // FieldPayURL holds the string denoting the pay_url field in the database. + FieldPayURL = "pay_url" + // FieldQrCode holds the string denoting the qr_code field in the database. + FieldQrCode = "qr_code" + // FieldQrCodeImg holds the string denoting the qr_code_img field in the database. + FieldQrCodeImg = "qr_code_img" + // FieldOrderType holds the string denoting the order_type field in the database. + FieldOrderType = "order_type" + // FieldPlanID holds the string denoting the plan_id field in the database. + FieldPlanID = "plan_id" + // FieldSubscriptionGroupID holds the string denoting the subscription_group_id field in the database. + FieldSubscriptionGroupID = "subscription_group_id" + // FieldSubscriptionDays holds the string denoting the subscription_days field in the database. + FieldSubscriptionDays = "subscription_days" + // FieldProviderInstanceID holds the string denoting the provider_instance_id field in the database. + FieldProviderInstanceID = "provider_instance_id" + // FieldStatus holds the string denoting the status field in the database. + FieldStatus = "status" + // FieldRefundAmount holds the string denoting the refund_amount field in the database. + FieldRefundAmount = "refund_amount" + // FieldRefundReason holds the string denoting the refund_reason field in the database. + FieldRefundReason = "refund_reason" + // FieldRefundAt holds the string denoting the refund_at field in the database. + FieldRefundAt = "refund_at" + // FieldForceRefund holds the string denoting the force_refund field in the database. + FieldForceRefund = "force_refund" + // FieldRefundRequestedAt holds the string denoting the refund_requested_at field in the database. + FieldRefundRequestedAt = "refund_requested_at" + // FieldRefundRequestReason holds the string denoting the refund_request_reason field in the database. + FieldRefundRequestReason = "refund_request_reason" + // FieldRefundRequestedBy holds the string denoting the refund_requested_by field in the database. + FieldRefundRequestedBy = "refund_requested_by" + // FieldExpiresAt holds the string denoting the expires_at field in the database. + FieldExpiresAt = "expires_at" + // FieldPaidAt holds the string denoting the paid_at field in the database. + FieldPaidAt = "paid_at" + // FieldCompletedAt holds the string denoting the completed_at field in the database. + FieldCompletedAt = "completed_at" + // FieldFailedAt holds the string denoting the failed_at field in the database. + FieldFailedAt = "failed_at" + // FieldFailedReason holds the string denoting the failed_reason field in the database. + FieldFailedReason = "failed_reason" + // FieldClientIP holds the string denoting the client_ip field in the database. + FieldClientIP = "client_ip" + // FieldSrcHost holds the string denoting the src_host field in the database. + FieldSrcHost = "src_host" + // FieldSrcURL holds the string denoting the src_url field in the database. + FieldSrcURL = "src_url" + // 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" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the paymentorder in the database. + Table = "payment_orders" + // UserTable is the table that holds the user relation/edge. + UserTable = "payment_orders" + // 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 paymentorder fields. +var Columns = []string{ + FieldID, + FieldUserID, + FieldUserEmail, + FieldUserName, + FieldUserNotes, + FieldAmount, + FieldPayAmount, + FieldFeeRate, + FieldRechargeCode, + FieldOutTradeNo, + FieldPaymentType, + FieldPaymentTradeNo, + FieldPayURL, + FieldQrCode, + FieldQrCodeImg, + FieldOrderType, + FieldPlanID, + FieldSubscriptionGroupID, + FieldSubscriptionDays, + FieldProviderInstanceID, + FieldStatus, + FieldRefundAmount, + FieldRefundReason, + FieldRefundAt, + FieldForceRefund, + FieldRefundRequestedAt, + FieldRefundRequestReason, + FieldRefundRequestedBy, + FieldExpiresAt, + FieldPaidAt, + FieldCompletedAt, + FieldFailedAt, + FieldFailedReason, + FieldClientIP, + FieldSrcHost, + FieldSrcURL, + 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 ( + // UserEmailValidator is a validator for the "user_email" field. It is called by the builders before save. + UserEmailValidator func(string) error + // UserNameValidator is a validator for the "user_name" field. It is called by the builders before save. + UserNameValidator func(string) error + // DefaultFeeRate holds the default value on creation for the "fee_rate" field. + DefaultFeeRate float64 + // RechargeCodeValidator is a validator for the "recharge_code" field. It is called by the builders before save. + RechargeCodeValidator func(string) error + // DefaultOutTradeNo holds the default value on creation for the "out_trade_no" field. + DefaultOutTradeNo string + // OutTradeNoValidator is a validator for the "out_trade_no" field. It is called by the builders before save. + OutTradeNoValidator func(string) error + // PaymentTypeValidator is a validator for the "payment_type" field. It is called by the builders before save. + PaymentTypeValidator func(string) error + // PaymentTradeNoValidator is a validator for the "payment_trade_no" field. It is called by the builders before save. + PaymentTradeNoValidator func(string) error + // DefaultOrderType holds the default value on creation for the "order_type" field. + DefaultOrderType string + // OrderTypeValidator is a validator for the "order_type" field. It is called by the builders before save. + OrderTypeValidator func(string) error + // ProviderInstanceIDValidator is a validator for the "provider_instance_id" field. It is called by the builders before save. + ProviderInstanceIDValidator func(string) error + // 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 + // DefaultRefundAmount holds the default value on creation for the "refund_amount" field. + DefaultRefundAmount float64 + // DefaultForceRefund holds the default value on creation for the "force_refund" field. + DefaultForceRefund bool + // RefundRequestedByValidator is a validator for the "refund_requested_by" field. It is called by the builders before save. + RefundRequestedByValidator func(string) error + // ClientIPValidator is a validator for the "client_ip" field. It is called by the builders before save. + ClientIPValidator func(string) error + // SrcHostValidator is a validator for the "src_host" field. It is called by the builders before save. + SrcHostValidator 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 PaymentOrder 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() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByUserEmail orders the results by the user_email field. +func ByUserEmail(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserEmail, opts...).ToFunc() +} + +// ByUserName orders the results by the user_name field. +func ByUserName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserName, opts...).ToFunc() +} + +// ByUserNotes orders the results by the user_notes field. +func ByUserNotes(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserNotes, opts...).ToFunc() +} + +// ByAmount orders the results by the amount field. +func ByAmount(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAmount, opts...).ToFunc() +} + +// ByPayAmount orders the results by the pay_amount field. +func ByPayAmount(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPayAmount, opts...).ToFunc() +} + +// ByFeeRate orders the results by the fee_rate field. +func ByFeeRate(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFeeRate, opts...).ToFunc() +} + +// ByRechargeCode orders the results by the recharge_code field. +func ByRechargeCode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRechargeCode, opts...).ToFunc() +} + +// ByOutTradeNo orders the results by the out_trade_no field. +func ByOutTradeNo(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOutTradeNo, opts...).ToFunc() +} + +// ByPaymentType orders the results by the payment_type field. +func ByPaymentType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPaymentType, opts...).ToFunc() +} + +// ByPaymentTradeNo orders the results by the payment_trade_no field. +func ByPaymentTradeNo(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPaymentTradeNo, opts...).ToFunc() +} + +// ByPayURL orders the results by the pay_url field. +func ByPayURL(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPayURL, opts...).ToFunc() +} + +// ByQrCode orders the results by the qr_code field. +func ByQrCode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldQrCode, opts...).ToFunc() +} + +// ByQrCodeImg orders the results by the qr_code_img field. +func ByQrCodeImg(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldQrCodeImg, opts...).ToFunc() +} + +// ByOrderType orders the results by the order_type field. +func ByOrderType(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOrderType, opts...).ToFunc() +} + +// ByPlanID orders the results by the plan_id field. +func ByPlanID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPlanID, opts...).ToFunc() +} + +// BySubscriptionGroupID orders the results by the subscription_group_id field. +func BySubscriptionGroupID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSubscriptionGroupID, opts...).ToFunc() +} + +// BySubscriptionDays orders the results by the subscription_days field. +func BySubscriptionDays(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSubscriptionDays, opts...).ToFunc() +} + +// ByProviderInstanceID orders the results by the provider_instance_id field. +func ByProviderInstanceID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldProviderInstanceID, opts...).ToFunc() +} + +// ByStatus orders the results by the status field. +func ByStatus(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStatus, opts...).ToFunc() +} + +// ByRefundAmount orders the results by the refund_amount field. +func ByRefundAmount(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundAmount, opts...).ToFunc() +} + +// ByRefundReason orders the results by the refund_reason field. +func ByRefundReason(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundReason, opts...).ToFunc() +} + +// ByRefundAt orders the results by the refund_at field. +func ByRefundAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundAt, opts...).ToFunc() +} + +// ByForceRefund orders the results by the force_refund field. +func ByForceRefund(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldForceRefund, opts...).ToFunc() +} + +// ByRefundRequestedAt orders the results by the refund_requested_at field. +func ByRefundRequestedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundRequestedAt, opts...).ToFunc() +} + +// ByRefundRequestReason orders the results by the refund_request_reason field. +func ByRefundRequestReason(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundRequestReason, opts...).ToFunc() +} + +// ByRefundRequestedBy orders the results by the refund_requested_by field. +func ByRefundRequestedBy(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundRequestedBy, opts...).ToFunc() +} + +// ByExpiresAt orders the results by the expires_at field. +func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldExpiresAt, opts...).ToFunc() +} + +// ByPaidAt orders the results by the paid_at field. +func ByPaidAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPaidAt, opts...).ToFunc() +} + +// ByCompletedAt orders the results by the completed_at field. +func ByCompletedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCompletedAt, opts...).ToFunc() +} + +// ByFailedAt orders the results by the failed_at field. +func ByFailedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFailedAt, opts...).ToFunc() +} + +// ByFailedReason orders the results by the failed_reason field. +func ByFailedReason(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFailedReason, opts...).ToFunc() +} + +// ByClientIP orders the results by the client_ip field. +func ByClientIP(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldClientIP, opts...).ToFunc() +} + +// BySrcHost orders the results by the src_host field. +func BySrcHost(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSrcHost, opts...).ToFunc() +} + +// BySrcURL orders the results by the src_url field. +func BySrcURL(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSrcURL, 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() +} + +// 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 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/paymentorder/where.go b/backend/ent/paymentorder/where.go new file mode 100644 index 0000000000000000000000000000000000000000..78520fac4286fd7281262789aa3154697f1e3951 --- /dev/null +++ b/backend/ent/paymentorder/where.go @@ -0,0 +1,2389 @@ +// Code generated by ent, DO NOT EDIT. + +package paymentorder + +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.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldID, id)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserID, v)) +} + +// UserEmail applies equality check predicate on the "user_email" field. It's identical to UserEmailEQ. +func UserEmail(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserEmail, v)) +} + +// UserName applies equality check predicate on the "user_name" field. It's identical to UserNameEQ. +func UserName(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserName, v)) +} + +// UserNotes applies equality check predicate on the "user_notes" field. It's identical to UserNotesEQ. +func UserNotes(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserNotes, v)) +} + +// Amount applies equality check predicate on the "amount" field. It's identical to AmountEQ. +func Amount(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldAmount, v)) +} + +// PayAmount applies equality check predicate on the "pay_amount" field. It's identical to PayAmountEQ. +func PayAmount(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPayAmount, v)) +} + +// FeeRate applies equality check predicate on the "fee_rate" field. It's identical to FeeRateEQ. +func FeeRate(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldFeeRate, v)) +} + +// RechargeCode applies equality check predicate on the "recharge_code" field. It's identical to RechargeCodeEQ. +func RechargeCode(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRechargeCode, v)) +} + +// OutTradeNo applies equality check predicate on the "out_trade_no" field. It's identical to OutTradeNoEQ. +func OutTradeNo(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldOutTradeNo, v)) +} + +// PaymentType applies equality check predicate on the "payment_type" field. It's identical to PaymentTypeEQ. +func PaymentType(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentType, v)) +} + +// PaymentTradeNo applies equality check predicate on the "payment_trade_no" field. It's identical to PaymentTradeNoEQ. +func PaymentTradeNo(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentTradeNo, v)) +} + +// PayURL applies equality check predicate on the "pay_url" field. It's identical to PayURLEQ. +func PayURL(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPayURL, v)) +} + +// QrCode applies equality check predicate on the "qr_code" field. It's identical to QrCodeEQ. +func QrCode(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldQrCode, v)) +} + +// QrCodeImg applies equality check predicate on the "qr_code_img" field. It's identical to QrCodeImgEQ. +func QrCodeImg(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldQrCodeImg, v)) +} + +// OrderType applies equality check predicate on the "order_type" field. It's identical to OrderTypeEQ. +func OrderType(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldOrderType, v)) +} + +// PlanID applies equality check predicate on the "plan_id" field. It's identical to PlanIDEQ. +func PlanID(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPlanID, v)) +} + +// SubscriptionGroupID applies equality check predicate on the "subscription_group_id" field. It's identical to SubscriptionGroupIDEQ. +func SubscriptionGroupID(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionGroupID, v)) +} + +// SubscriptionDays applies equality check predicate on the "subscription_days" field. It's identical to SubscriptionDaysEQ. +func SubscriptionDays(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionDays, v)) +} + +// ProviderInstanceID applies equality check predicate on the "provider_instance_id" field. It's identical to ProviderInstanceIDEQ. +func ProviderInstanceID(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldProviderInstanceID, v)) +} + +// Status applies equality check predicate on the "status" field. It's identical to StatusEQ. +func Status(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldStatus, v)) +} + +// RefundAmount applies equality check predicate on the "refund_amount" field. It's identical to RefundAmountEQ. +func RefundAmount(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAmount, v)) +} + +// RefundReason applies equality check predicate on the "refund_reason" field. It's identical to RefundReasonEQ. +func RefundReason(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundReason, v)) +} + +// RefundAt applies equality check predicate on the "refund_at" field. It's identical to RefundAtEQ. +func RefundAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAt, v)) +} + +// ForceRefund applies equality check predicate on the "force_refund" field. It's identical to ForceRefundEQ. +func ForceRefund(v bool) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldForceRefund, v)) +} + +// RefundRequestedAt applies equality check predicate on the "refund_requested_at" field. It's identical to RefundRequestedAtEQ. +func RefundRequestedAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedAt, v)) +} + +// RefundRequestReason applies equality check predicate on the "refund_request_reason" field. It's identical to RefundRequestReasonEQ. +func RefundRequestReason(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestReason, v)) +} + +// RefundRequestedBy applies equality check predicate on the "refund_requested_by" field. It's identical to RefundRequestedByEQ. +func RefundRequestedBy(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedBy, v)) +} + +// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. +func ExpiresAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldExpiresAt, v)) +} + +// PaidAt applies equality check predicate on the "paid_at" field. It's identical to PaidAtEQ. +func PaidAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPaidAt, v)) +} + +// CompletedAt applies equality check predicate on the "completed_at" field. It's identical to CompletedAtEQ. +func CompletedAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldCompletedAt, v)) +} + +// FailedAt applies equality check predicate on the "failed_at" field. It's identical to FailedAtEQ. +func FailedAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldFailedAt, v)) +} + +// FailedReason applies equality check predicate on the "failed_reason" field. It's identical to FailedReasonEQ. +func FailedReason(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldFailedReason, v)) +} + +// ClientIP applies equality check predicate on the "client_ip" field. It's identical to ClientIPEQ. +func ClientIP(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldClientIP, v)) +} + +// SrcHost applies equality check predicate on the "src_host" field. It's identical to SrcHostEQ. +func SrcHost(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSrcHost, v)) +} + +// SrcURL applies equality check predicate on the "src_url" field. It's identical to SrcURLEQ. +func SrcURL(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSrcURL, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(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.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldUserID, vs...)) +} + +// UserEmailEQ applies the EQ predicate on the "user_email" field. +func UserEmailEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserEmail, v)) +} + +// UserEmailNEQ applies the NEQ predicate on the "user_email" field. +func UserEmailNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldUserEmail, v)) +} + +// UserEmailIn applies the In predicate on the "user_email" field. +func UserEmailIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldUserEmail, vs...)) +} + +// UserEmailNotIn applies the NotIn predicate on the "user_email" field. +func UserEmailNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldUserEmail, vs...)) +} + +// UserEmailGT applies the GT predicate on the "user_email" field. +func UserEmailGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldUserEmail, v)) +} + +// UserEmailGTE applies the GTE predicate on the "user_email" field. +func UserEmailGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldUserEmail, v)) +} + +// UserEmailLT applies the LT predicate on the "user_email" field. +func UserEmailLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldUserEmail, v)) +} + +// UserEmailLTE applies the LTE predicate on the "user_email" field. +func UserEmailLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldUserEmail, v)) +} + +// UserEmailContains applies the Contains predicate on the "user_email" field. +func UserEmailContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldUserEmail, v)) +} + +// UserEmailHasPrefix applies the HasPrefix predicate on the "user_email" field. +func UserEmailHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldUserEmail, v)) +} + +// UserEmailHasSuffix applies the HasSuffix predicate on the "user_email" field. +func UserEmailHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldUserEmail, v)) +} + +// UserEmailEqualFold applies the EqualFold predicate on the "user_email" field. +func UserEmailEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldUserEmail, v)) +} + +// UserEmailContainsFold applies the ContainsFold predicate on the "user_email" field. +func UserEmailContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldUserEmail, v)) +} + +// UserNameEQ applies the EQ predicate on the "user_name" field. +func UserNameEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserName, v)) +} + +// UserNameNEQ applies the NEQ predicate on the "user_name" field. +func UserNameNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldUserName, v)) +} + +// UserNameIn applies the In predicate on the "user_name" field. +func UserNameIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldUserName, vs...)) +} + +// UserNameNotIn applies the NotIn predicate on the "user_name" field. +func UserNameNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldUserName, vs...)) +} + +// UserNameGT applies the GT predicate on the "user_name" field. +func UserNameGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldUserName, v)) +} + +// UserNameGTE applies the GTE predicate on the "user_name" field. +func UserNameGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldUserName, v)) +} + +// UserNameLT applies the LT predicate on the "user_name" field. +func UserNameLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldUserName, v)) +} + +// UserNameLTE applies the LTE predicate on the "user_name" field. +func UserNameLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldUserName, v)) +} + +// UserNameContains applies the Contains predicate on the "user_name" field. +func UserNameContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldUserName, v)) +} + +// UserNameHasPrefix applies the HasPrefix predicate on the "user_name" field. +func UserNameHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldUserName, v)) +} + +// UserNameHasSuffix applies the HasSuffix predicate on the "user_name" field. +func UserNameHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldUserName, v)) +} + +// UserNameEqualFold applies the EqualFold predicate on the "user_name" field. +func UserNameEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldUserName, v)) +} + +// UserNameContainsFold applies the ContainsFold predicate on the "user_name" field. +func UserNameContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldUserName, v)) +} + +// UserNotesEQ applies the EQ predicate on the "user_notes" field. +func UserNotesEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUserNotes, v)) +} + +// UserNotesNEQ applies the NEQ predicate on the "user_notes" field. +func UserNotesNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldUserNotes, v)) +} + +// UserNotesIn applies the In predicate on the "user_notes" field. +func UserNotesIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldUserNotes, vs...)) +} + +// UserNotesNotIn applies the NotIn predicate on the "user_notes" field. +func UserNotesNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldUserNotes, vs...)) +} + +// UserNotesGT applies the GT predicate on the "user_notes" field. +func UserNotesGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldUserNotes, v)) +} + +// UserNotesGTE applies the GTE predicate on the "user_notes" field. +func UserNotesGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldUserNotes, v)) +} + +// UserNotesLT applies the LT predicate on the "user_notes" field. +func UserNotesLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldUserNotes, v)) +} + +// UserNotesLTE applies the LTE predicate on the "user_notes" field. +func UserNotesLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldUserNotes, v)) +} + +// UserNotesContains applies the Contains predicate on the "user_notes" field. +func UserNotesContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldUserNotes, v)) +} + +// UserNotesHasPrefix applies the HasPrefix predicate on the "user_notes" field. +func UserNotesHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldUserNotes, v)) +} + +// UserNotesHasSuffix applies the HasSuffix predicate on the "user_notes" field. +func UserNotesHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldUserNotes, v)) +} + +// UserNotesIsNil applies the IsNil predicate on the "user_notes" field. +func UserNotesIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldUserNotes)) +} + +// UserNotesNotNil applies the NotNil predicate on the "user_notes" field. +func UserNotesNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldUserNotes)) +} + +// UserNotesEqualFold applies the EqualFold predicate on the "user_notes" field. +func UserNotesEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldUserNotes, v)) +} + +// UserNotesContainsFold applies the ContainsFold predicate on the "user_notes" field. +func UserNotesContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldUserNotes, v)) +} + +// AmountEQ applies the EQ predicate on the "amount" field. +func AmountEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldAmount, v)) +} + +// AmountNEQ applies the NEQ predicate on the "amount" field. +func AmountNEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldAmount, v)) +} + +// AmountIn applies the In predicate on the "amount" field. +func AmountIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldAmount, vs...)) +} + +// AmountNotIn applies the NotIn predicate on the "amount" field. +func AmountNotIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldAmount, vs...)) +} + +// AmountGT applies the GT predicate on the "amount" field. +func AmountGT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldAmount, v)) +} + +// AmountGTE applies the GTE predicate on the "amount" field. +func AmountGTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldAmount, v)) +} + +// AmountLT applies the LT predicate on the "amount" field. +func AmountLT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldAmount, v)) +} + +// AmountLTE applies the LTE predicate on the "amount" field. +func AmountLTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldAmount, v)) +} + +// PayAmountEQ applies the EQ predicate on the "pay_amount" field. +func PayAmountEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPayAmount, v)) +} + +// PayAmountNEQ applies the NEQ predicate on the "pay_amount" field. +func PayAmountNEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldPayAmount, v)) +} + +// PayAmountIn applies the In predicate on the "pay_amount" field. +func PayAmountIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldPayAmount, vs...)) +} + +// PayAmountNotIn applies the NotIn predicate on the "pay_amount" field. +func PayAmountNotIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldPayAmount, vs...)) +} + +// PayAmountGT applies the GT predicate on the "pay_amount" field. +func PayAmountGT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldPayAmount, v)) +} + +// PayAmountGTE applies the GTE predicate on the "pay_amount" field. +func PayAmountGTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldPayAmount, v)) +} + +// PayAmountLT applies the LT predicate on the "pay_amount" field. +func PayAmountLT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldPayAmount, v)) +} + +// PayAmountLTE applies the LTE predicate on the "pay_amount" field. +func PayAmountLTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldPayAmount, v)) +} + +// FeeRateEQ applies the EQ predicate on the "fee_rate" field. +func FeeRateEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldFeeRate, v)) +} + +// FeeRateNEQ applies the NEQ predicate on the "fee_rate" field. +func FeeRateNEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldFeeRate, v)) +} + +// FeeRateIn applies the In predicate on the "fee_rate" field. +func FeeRateIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldFeeRate, vs...)) +} + +// FeeRateNotIn applies the NotIn predicate on the "fee_rate" field. +func FeeRateNotIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldFeeRate, vs...)) +} + +// FeeRateGT applies the GT predicate on the "fee_rate" field. +func FeeRateGT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldFeeRate, v)) +} + +// FeeRateGTE applies the GTE predicate on the "fee_rate" field. +func FeeRateGTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldFeeRate, v)) +} + +// FeeRateLT applies the LT predicate on the "fee_rate" field. +func FeeRateLT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldFeeRate, v)) +} + +// FeeRateLTE applies the LTE predicate on the "fee_rate" field. +func FeeRateLTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldFeeRate, v)) +} + +// RechargeCodeEQ applies the EQ predicate on the "recharge_code" field. +func RechargeCodeEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRechargeCode, v)) +} + +// RechargeCodeNEQ applies the NEQ predicate on the "recharge_code" field. +func RechargeCodeNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRechargeCode, v)) +} + +// RechargeCodeIn applies the In predicate on the "recharge_code" field. +func RechargeCodeIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRechargeCode, vs...)) +} + +// RechargeCodeNotIn applies the NotIn predicate on the "recharge_code" field. +func RechargeCodeNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRechargeCode, vs...)) +} + +// RechargeCodeGT applies the GT predicate on the "recharge_code" field. +func RechargeCodeGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRechargeCode, v)) +} + +// RechargeCodeGTE applies the GTE predicate on the "recharge_code" field. +func RechargeCodeGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRechargeCode, v)) +} + +// RechargeCodeLT applies the LT predicate on the "recharge_code" field. +func RechargeCodeLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRechargeCode, v)) +} + +// RechargeCodeLTE applies the LTE predicate on the "recharge_code" field. +func RechargeCodeLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRechargeCode, v)) +} + +// RechargeCodeContains applies the Contains predicate on the "recharge_code" field. +func RechargeCodeContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldRechargeCode, v)) +} + +// RechargeCodeHasPrefix applies the HasPrefix predicate on the "recharge_code" field. +func RechargeCodeHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRechargeCode, v)) +} + +// RechargeCodeHasSuffix applies the HasSuffix predicate on the "recharge_code" field. +func RechargeCodeHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRechargeCode, v)) +} + +// RechargeCodeEqualFold applies the EqualFold predicate on the "recharge_code" field. +func RechargeCodeEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldRechargeCode, v)) +} + +// RechargeCodeContainsFold applies the ContainsFold predicate on the "recharge_code" field. +func RechargeCodeContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldRechargeCode, v)) +} + +// OutTradeNoEQ applies the EQ predicate on the "out_trade_no" field. +func OutTradeNoEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldOutTradeNo, v)) +} + +// OutTradeNoNEQ applies the NEQ predicate on the "out_trade_no" field. +func OutTradeNoNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldOutTradeNo, v)) +} + +// OutTradeNoIn applies the In predicate on the "out_trade_no" field. +func OutTradeNoIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldOutTradeNo, vs...)) +} + +// OutTradeNoNotIn applies the NotIn predicate on the "out_trade_no" field. +func OutTradeNoNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldOutTradeNo, vs...)) +} + +// OutTradeNoGT applies the GT predicate on the "out_trade_no" field. +func OutTradeNoGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldOutTradeNo, v)) +} + +// OutTradeNoGTE applies the GTE predicate on the "out_trade_no" field. +func OutTradeNoGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldOutTradeNo, v)) +} + +// OutTradeNoLT applies the LT predicate on the "out_trade_no" field. +func OutTradeNoLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldOutTradeNo, v)) +} + +// OutTradeNoLTE applies the LTE predicate on the "out_trade_no" field. +func OutTradeNoLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldOutTradeNo, v)) +} + +// OutTradeNoContains applies the Contains predicate on the "out_trade_no" field. +func OutTradeNoContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldOutTradeNo, v)) +} + +// OutTradeNoHasPrefix applies the HasPrefix predicate on the "out_trade_no" field. +func OutTradeNoHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldOutTradeNo, v)) +} + +// OutTradeNoHasSuffix applies the HasSuffix predicate on the "out_trade_no" field. +func OutTradeNoHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldOutTradeNo, v)) +} + +// OutTradeNoEqualFold applies the EqualFold predicate on the "out_trade_no" field. +func OutTradeNoEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldOutTradeNo, v)) +} + +// OutTradeNoContainsFold applies the ContainsFold predicate on the "out_trade_no" field. +func OutTradeNoContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldOutTradeNo, v)) +} + +// PaymentTypeEQ applies the EQ predicate on the "payment_type" field. +func PaymentTypeEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentType, v)) +} + +// PaymentTypeNEQ applies the NEQ predicate on the "payment_type" field. +func PaymentTypeNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldPaymentType, v)) +} + +// PaymentTypeIn applies the In predicate on the "payment_type" field. +func PaymentTypeIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldPaymentType, vs...)) +} + +// PaymentTypeNotIn applies the NotIn predicate on the "payment_type" field. +func PaymentTypeNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldPaymentType, vs...)) +} + +// PaymentTypeGT applies the GT predicate on the "payment_type" field. +func PaymentTypeGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldPaymentType, v)) +} + +// PaymentTypeGTE applies the GTE predicate on the "payment_type" field. +func PaymentTypeGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldPaymentType, v)) +} + +// PaymentTypeLT applies the LT predicate on the "payment_type" field. +func PaymentTypeLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldPaymentType, v)) +} + +// PaymentTypeLTE applies the LTE predicate on the "payment_type" field. +func PaymentTypeLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldPaymentType, v)) +} + +// PaymentTypeContains applies the Contains predicate on the "payment_type" field. +func PaymentTypeContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldPaymentType, v)) +} + +// PaymentTypeHasPrefix applies the HasPrefix predicate on the "payment_type" field. +func PaymentTypeHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldPaymentType, v)) +} + +// PaymentTypeHasSuffix applies the HasSuffix predicate on the "payment_type" field. +func PaymentTypeHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldPaymentType, v)) +} + +// PaymentTypeEqualFold applies the EqualFold predicate on the "payment_type" field. +func PaymentTypeEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldPaymentType, v)) +} + +// PaymentTypeContainsFold applies the ContainsFold predicate on the "payment_type" field. +func PaymentTypeContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldPaymentType, v)) +} + +// PaymentTradeNoEQ applies the EQ predicate on the "payment_trade_no" field. +func PaymentTradeNoEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoNEQ applies the NEQ predicate on the "payment_trade_no" field. +func PaymentTradeNoNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoIn applies the In predicate on the "payment_trade_no" field. +func PaymentTradeNoIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldPaymentTradeNo, vs...)) +} + +// PaymentTradeNoNotIn applies the NotIn predicate on the "payment_trade_no" field. +func PaymentTradeNoNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldPaymentTradeNo, vs...)) +} + +// PaymentTradeNoGT applies the GT predicate on the "payment_trade_no" field. +func PaymentTradeNoGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoGTE applies the GTE predicate on the "payment_trade_no" field. +func PaymentTradeNoGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoLT applies the LT predicate on the "payment_trade_no" field. +func PaymentTradeNoLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoLTE applies the LTE predicate on the "payment_trade_no" field. +func PaymentTradeNoLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoContains applies the Contains predicate on the "payment_trade_no" field. +func PaymentTradeNoContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoHasPrefix applies the HasPrefix predicate on the "payment_trade_no" field. +func PaymentTradeNoHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoHasSuffix applies the HasSuffix predicate on the "payment_trade_no" field. +func PaymentTradeNoHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoEqualFold applies the EqualFold predicate on the "payment_trade_no" field. +func PaymentTradeNoEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldPaymentTradeNo, v)) +} + +// PaymentTradeNoContainsFold applies the ContainsFold predicate on the "payment_trade_no" field. +func PaymentTradeNoContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldPaymentTradeNo, v)) +} + +// PayURLEQ applies the EQ predicate on the "pay_url" field. +func PayURLEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPayURL, v)) +} + +// PayURLNEQ applies the NEQ predicate on the "pay_url" field. +func PayURLNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldPayURL, v)) +} + +// PayURLIn applies the In predicate on the "pay_url" field. +func PayURLIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldPayURL, vs...)) +} + +// PayURLNotIn applies the NotIn predicate on the "pay_url" field. +func PayURLNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldPayURL, vs...)) +} + +// PayURLGT applies the GT predicate on the "pay_url" field. +func PayURLGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldPayURL, v)) +} + +// PayURLGTE applies the GTE predicate on the "pay_url" field. +func PayURLGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldPayURL, v)) +} + +// PayURLLT applies the LT predicate on the "pay_url" field. +func PayURLLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldPayURL, v)) +} + +// PayURLLTE applies the LTE predicate on the "pay_url" field. +func PayURLLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldPayURL, v)) +} + +// PayURLContains applies the Contains predicate on the "pay_url" field. +func PayURLContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldPayURL, v)) +} + +// PayURLHasPrefix applies the HasPrefix predicate on the "pay_url" field. +func PayURLHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldPayURL, v)) +} + +// PayURLHasSuffix applies the HasSuffix predicate on the "pay_url" field. +func PayURLHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldPayURL, v)) +} + +// PayURLIsNil applies the IsNil predicate on the "pay_url" field. +func PayURLIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldPayURL)) +} + +// PayURLNotNil applies the NotNil predicate on the "pay_url" field. +func PayURLNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldPayURL)) +} + +// PayURLEqualFold applies the EqualFold predicate on the "pay_url" field. +func PayURLEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldPayURL, v)) +} + +// PayURLContainsFold applies the ContainsFold predicate on the "pay_url" field. +func PayURLContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldPayURL, v)) +} + +// QrCodeEQ applies the EQ predicate on the "qr_code" field. +func QrCodeEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldQrCode, v)) +} + +// QrCodeNEQ applies the NEQ predicate on the "qr_code" field. +func QrCodeNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldQrCode, v)) +} + +// QrCodeIn applies the In predicate on the "qr_code" field. +func QrCodeIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldQrCode, vs...)) +} + +// QrCodeNotIn applies the NotIn predicate on the "qr_code" field. +func QrCodeNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldQrCode, vs...)) +} + +// QrCodeGT applies the GT predicate on the "qr_code" field. +func QrCodeGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldQrCode, v)) +} + +// QrCodeGTE applies the GTE predicate on the "qr_code" field. +func QrCodeGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldQrCode, v)) +} + +// QrCodeLT applies the LT predicate on the "qr_code" field. +func QrCodeLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldQrCode, v)) +} + +// QrCodeLTE applies the LTE predicate on the "qr_code" field. +func QrCodeLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldQrCode, v)) +} + +// QrCodeContains applies the Contains predicate on the "qr_code" field. +func QrCodeContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldQrCode, v)) +} + +// QrCodeHasPrefix applies the HasPrefix predicate on the "qr_code" field. +func QrCodeHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldQrCode, v)) +} + +// QrCodeHasSuffix applies the HasSuffix predicate on the "qr_code" field. +func QrCodeHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldQrCode, v)) +} + +// QrCodeIsNil applies the IsNil predicate on the "qr_code" field. +func QrCodeIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldQrCode)) +} + +// QrCodeNotNil applies the NotNil predicate on the "qr_code" field. +func QrCodeNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldQrCode)) +} + +// QrCodeEqualFold applies the EqualFold predicate on the "qr_code" field. +func QrCodeEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldQrCode, v)) +} + +// QrCodeContainsFold applies the ContainsFold predicate on the "qr_code" field. +func QrCodeContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldQrCode, v)) +} + +// QrCodeImgEQ applies the EQ predicate on the "qr_code_img" field. +func QrCodeImgEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldQrCodeImg, v)) +} + +// QrCodeImgNEQ applies the NEQ predicate on the "qr_code_img" field. +func QrCodeImgNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldQrCodeImg, v)) +} + +// QrCodeImgIn applies the In predicate on the "qr_code_img" field. +func QrCodeImgIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldQrCodeImg, vs...)) +} + +// QrCodeImgNotIn applies the NotIn predicate on the "qr_code_img" field. +func QrCodeImgNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldQrCodeImg, vs...)) +} + +// QrCodeImgGT applies the GT predicate on the "qr_code_img" field. +func QrCodeImgGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldQrCodeImg, v)) +} + +// QrCodeImgGTE applies the GTE predicate on the "qr_code_img" field. +func QrCodeImgGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldQrCodeImg, v)) +} + +// QrCodeImgLT applies the LT predicate on the "qr_code_img" field. +func QrCodeImgLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldQrCodeImg, v)) +} + +// QrCodeImgLTE applies the LTE predicate on the "qr_code_img" field. +func QrCodeImgLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldQrCodeImg, v)) +} + +// QrCodeImgContains applies the Contains predicate on the "qr_code_img" field. +func QrCodeImgContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldQrCodeImg, v)) +} + +// QrCodeImgHasPrefix applies the HasPrefix predicate on the "qr_code_img" field. +func QrCodeImgHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldQrCodeImg, v)) +} + +// QrCodeImgHasSuffix applies the HasSuffix predicate on the "qr_code_img" field. +func QrCodeImgHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldQrCodeImg, v)) +} + +// QrCodeImgIsNil applies the IsNil predicate on the "qr_code_img" field. +func QrCodeImgIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldQrCodeImg)) +} + +// QrCodeImgNotNil applies the NotNil predicate on the "qr_code_img" field. +func QrCodeImgNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldQrCodeImg)) +} + +// QrCodeImgEqualFold applies the EqualFold predicate on the "qr_code_img" field. +func QrCodeImgEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldQrCodeImg, v)) +} + +// QrCodeImgContainsFold applies the ContainsFold predicate on the "qr_code_img" field. +func QrCodeImgContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldQrCodeImg, v)) +} + +// OrderTypeEQ applies the EQ predicate on the "order_type" field. +func OrderTypeEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldOrderType, v)) +} + +// OrderTypeNEQ applies the NEQ predicate on the "order_type" field. +func OrderTypeNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldOrderType, v)) +} + +// OrderTypeIn applies the In predicate on the "order_type" field. +func OrderTypeIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldOrderType, vs...)) +} + +// OrderTypeNotIn applies the NotIn predicate on the "order_type" field. +func OrderTypeNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldOrderType, vs...)) +} + +// OrderTypeGT applies the GT predicate on the "order_type" field. +func OrderTypeGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldOrderType, v)) +} + +// OrderTypeGTE applies the GTE predicate on the "order_type" field. +func OrderTypeGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldOrderType, v)) +} + +// OrderTypeLT applies the LT predicate on the "order_type" field. +func OrderTypeLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldOrderType, v)) +} + +// OrderTypeLTE applies the LTE predicate on the "order_type" field. +func OrderTypeLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldOrderType, v)) +} + +// OrderTypeContains applies the Contains predicate on the "order_type" field. +func OrderTypeContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldOrderType, v)) +} + +// OrderTypeHasPrefix applies the HasPrefix predicate on the "order_type" field. +func OrderTypeHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldOrderType, v)) +} + +// OrderTypeHasSuffix applies the HasSuffix predicate on the "order_type" field. +func OrderTypeHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldOrderType, v)) +} + +// OrderTypeEqualFold applies the EqualFold predicate on the "order_type" field. +func OrderTypeEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldOrderType, v)) +} + +// OrderTypeContainsFold applies the ContainsFold predicate on the "order_type" field. +func OrderTypeContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldOrderType, v)) +} + +// PlanIDEQ applies the EQ predicate on the "plan_id" field. +func PlanIDEQ(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPlanID, v)) +} + +// PlanIDNEQ applies the NEQ predicate on the "plan_id" field. +func PlanIDNEQ(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldPlanID, v)) +} + +// PlanIDIn applies the In predicate on the "plan_id" field. +func PlanIDIn(vs ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldPlanID, vs...)) +} + +// PlanIDNotIn applies the NotIn predicate on the "plan_id" field. +func PlanIDNotIn(vs ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldPlanID, vs...)) +} + +// PlanIDGT applies the GT predicate on the "plan_id" field. +func PlanIDGT(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldPlanID, v)) +} + +// PlanIDGTE applies the GTE predicate on the "plan_id" field. +func PlanIDGTE(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldPlanID, v)) +} + +// PlanIDLT applies the LT predicate on the "plan_id" field. +func PlanIDLT(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldPlanID, v)) +} + +// PlanIDLTE applies the LTE predicate on the "plan_id" field. +func PlanIDLTE(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldPlanID, v)) +} + +// PlanIDIsNil applies the IsNil predicate on the "plan_id" field. +func PlanIDIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldPlanID)) +} + +// PlanIDNotNil applies the NotNil predicate on the "plan_id" field. +func PlanIDNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldPlanID)) +} + +// SubscriptionGroupIDEQ applies the EQ predicate on the "subscription_group_id" field. +func SubscriptionGroupIDEQ(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionGroupID, v)) +} + +// SubscriptionGroupIDNEQ applies the NEQ predicate on the "subscription_group_id" field. +func SubscriptionGroupIDNEQ(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldSubscriptionGroupID, v)) +} + +// SubscriptionGroupIDIn applies the In predicate on the "subscription_group_id" field. +func SubscriptionGroupIDIn(vs ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldSubscriptionGroupID, vs...)) +} + +// SubscriptionGroupIDNotIn applies the NotIn predicate on the "subscription_group_id" field. +func SubscriptionGroupIDNotIn(vs ...int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldSubscriptionGroupID, vs...)) +} + +// SubscriptionGroupIDGT applies the GT predicate on the "subscription_group_id" field. +func SubscriptionGroupIDGT(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldSubscriptionGroupID, v)) +} + +// SubscriptionGroupIDGTE applies the GTE predicate on the "subscription_group_id" field. +func SubscriptionGroupIDGTE(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldSubscriptionGroupID, v)) +} + +// SubscriptionGroupIDLT applies the LT predicate on the "subscription_group_id" field. +func SubscriptionGroupIDLT(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldSubscriptionGroupID, v)) +} + +// SubscriptionGroupIDLTE applies the LTE predicate on the "subscription_group_id" field. +func SubscriptionGroupIDLTE(v int64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldSubscriptionGroupID, v)) +} + +// SubscriptionGroupIDIsNil applies the IsNil predicate on the "subscription_group_id" field. +func SubscriptionGroupIDIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldSubscriptionGroupID)) +} + +// SubscriptionGroupIDNotNil applies the NotNil predicate on the "subscription_group_id" field. +func SubscriptionGroupIDNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldSubscriptionGroupID)) +} + +// SubscriptionDaysEQ applies the EQ predicate on the "subscription_days" field. +func SubscriptionDaysEQ(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSubscriptionDays, v)) +} + +// SubscriptionDaysNEQ applies the NEQ predicate on the "subscription_days" field. +func SubscriptionDaysNEQ(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldSubscriptionDays, v)) +} + +// SubscriptionDaysIn applies the In predicate on the "subscription_days" field. +func SubscriptionDaysIn(vs ...int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldSubscriptionDays, vs...)) +} + +// SubscriptionDaysNotIn applies the NotIn predicate on the "subscription_days" field. +func SubscriptionDaysNotIn(vs ...int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldSubscriptionDays, vs...)) +} + +// SubscriptionDaysGT applies the GT predicate on the "subscription_days" field. +func SubscriptionDaysGT(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldSubscriptionDays, v)) +} + +// SubscriptionDaysGTE applies the GTE predicate on the "subscription_days" field. +func SubscriptionDaysGTE(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldSubscriptionDays, v)) +} + +// SubscriptionDaysLT applies the LT predicate on the "subscription_days" field. +func SubscriptionDaysLT(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldSubscriptionDays, v)) +} + +// SubscriptionDaysLTE applies the LTE predicate on the "subscription_days" field. +func SubscriptionDaysLTE(v int) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldSubscriptionDays, v)) +} + +// SubscriptionDaysIsNil applies the IsNil predicate on the "subscription_days" field. +func SubscriptionDaysIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldSubscriptionDays)) +} + +// SubscriptionDaysNotNil applies the NotNil predicate on the "subscription_days" field. +func SubscriptionDaysNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldSubscriptionDays)) +} + +// ProviderInstanceIDEQ applies the EQ predicate on the "provider_instance_id" field. +func ProviderInstanceIDEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDNEQ applies the NEQ predicate on the "provider_instance_id" field. +func ProviderInstanceIDNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDIn applies the In predicate on the "provider_instance_id" field. +func ProviderInstanceIDIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldProviderInstanceID, vs...)) +} + +// ProviderInstanceIDNotIn applies the NotIn predicate on the "provider_instance_id" field. +func ProviderInstanceIDNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldProviderInstanceID, vs...)) +} + +// ProviderInstanceIDGT applies the GT predicate on the "provider_instance_id" field. +func ProviderInstanceIDGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDGTE applies the GTE predicate on the "provider_instance_id" field. +func ProviderInstanceIDGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDLT applies the LT predicate on the "provider_instance_id" field. +func ProviderInstanceIDLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDLTE applies the LTE predicate on the "provider_instance_id" field. +func ProviderInstanceIDLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDContains applies the Contains predicate on the "provider_instance_id" field. +func ProviderInstanceIDContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDHasPrefix applies the HasPrefix predicate on the "provider_instance_id" field. +func ProviderInstanceIDHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDHasSuffix applies the HasSuffix predicate on the "provider_instance_id" field. +func ProviderInstanceIDHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDIsNil applies the IsNil predicate on the "provider_instance_id" field. +func ProviderInstanceIDIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldProviderInstanceID)) +} + +// ProviderInstanceIDNotNil applies the NotNil predicate on the "provider_instance_id" field. +func ProviderInstanceIDNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldProviderInstanceID)) +} + +// ProviderInstanceIDEqualFold applies the EqualFold predicate on the "provider_instance_id" field. +func ProviderInstanceIDEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldProviderInstanceID, v)) +} + +// ProviderInstanceIDContainsFold applies the ContainsFold predicate on the "provider_instance_id" field. +func ProviderInstanceIDContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldProviderInstanceID, v)) +} + +// StatusEQ applies the EQ predicate on the "status" field. +func StatusEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldStatus, v)) +} + +// StatusNEQ applies the NEQ predicate on the "status" field. +func StatusNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldStatus, v)) +} + +// StatusIn applies the In predicate on the "status" field. +func StatusIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldStatus, vs...)) +} + +// StatusNotIn applies the NotIn predicate on the "status" field. +func StatusNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldStatus, vs...)) +} + +// StatusGT applies the GT predicate on the "status" field. +func StatusGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldStatus, v)) +} + +// StatusGTE applies the GTE predicate on the "status" field. +func StatusGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldStatus, v)) +} + +// StatusLT applies the LT predicate on the "status" field. +func StatusLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldStatus, v)) +} + +// StatusLTE applies the LTE predicate on the "status" field. +func StatusLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldStatus, v)) +} + +// StatusContains applies the Contains predicate on the "status" field. +func StatusContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldStatus, v)) +} + +// StatusHasPrefix applies the HasPrefix predicate on the "status" field. +func StatusHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldStatus, v)) +} + +// StatusHasSuffix applies the HasSuffix predicate on the "status" field. +func StatusHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldStatus, v)) +} + +// StatusEqualFold applies the EqualFold predicate on the "status" field. +func StatusEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldStatus, v)) +} + +// StatusContainsFold applies the ContainsFold predicate on the "status" field. +func StatusContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldStatus, v)) +} + +// RefundAmountEQ applies the EQ predicate on the "refund_amount" field. +func RefundAmountEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAmount, v)) +} + +// RefundAmountNEQ applies the NEQ predicate on the "refund_amount" field. +func RefundAmountNEQ(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundAmount, v)) +} + +// RefundAmountIn applies the In predicate on the "refund_amount" field. +func RefundAmountIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundAmount, vs...)) +} + +// RefundAmountNotIn applies the NotIn predicate on the "refund_amount" field. +func RefundAmountNotIn(vs ...float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundAmount, vs...)) +} + +// RefundAmountGT applies the GT predicate on the "refund_amount" field. +func RefundAmountGT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundAmount, v)) +} + +// RefundAmountGTE applies the GTE predicate on the "refund_amount" field. +func RefundAmountGTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundAmount, v)) +} + +// RefundAmountLT applies the LT predicate on the "refund_amount" field. +func RefundAmountLT(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundAmount, v)) +} + +// RefundAmountLTE applies the LTE predicate on the "refund_amount" field. +func RefundAmountLTE(v float64) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundAmount, v)) +} + +// RefundReasonEQ applies the EQ predicate on the "refund_reason" field. +func RefundReasonEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundReason, v)) +} + +// RefundReasonNEQ applies the NEQ predicate on the "refund_reason" field. +func RefundReasonNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundReason, v)) +} + +// RefundReasonIn applies the In predicate on the "refund_reason" field. +func RefundReasonIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundReason, vs...)) +} + +// RefundReasonNotIn applies the NotIn predicate on the "refund_reason" field. +func RefundReasonNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundReason, vs...)) +} + +// RefundReasonGT applies the GT predicate on the "refund_reason" field. +func RefundReasonGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundReason, v)) +} + +// RefundReasonGTE applies the GTE predicate on the "refund_reason" field. +func RefundReasonGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundReason, v)) +} + +// RefundReasonLT applies the LT predicate on the "refund_reason" field. +func RefundReasonLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundReason, v)) +} + +// RefundReasonLTE applies the LTE predicate on the "refund_reason" field. +func RefundReasonLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundReason, v)) +} + +// RefundReasonContains applies the Contains predicate on the "refund_reason" field. +func RefundReasonContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldRefundReason, v)) +} + +// RefundReasonHasPrefix applies the HasPrefix predicate on the "refund_reason" field. +func RefundReasonHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundReason, v)) +} + +// RefundReasonHasSuffix applies the HasSuffix predicate on the "refund_reason" field. +func RefundReasonHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundReason, v)) +} + +// RefundReasonIsNil applies the IsNil predicate on the "refund_reason" field. +func RefundReasonIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundReason)) +} + +// RefundReasonNotNil applies the NotNil predicate on the "refund_reason" field. +func RefundReasonNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundReason)) +} + +// RefundReasonEqualFold applies the EqualFold predicate on the "refund_reason" field. +func RefundReasonEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundReason, v)) +} + +// RefundReasonContainsFold applies the ContainsFold predicate on the "refund_reason" field. +func RefundReasonContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundReason, v)) +} + +// RefundAtEQ applies the EQ predicate on the "refund_at" field. +func RefundAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundAt, v)) +} + +// RefundAtNEQ applies the NEQ predicate on the "refund_at" field. +func RefundAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundAt, v)) +} + +// RefundAtIn applies the In predicate on the "refund_at" field. +func RefundAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundAt, vs...)) +} + +// RefundAtNotIn applies the NotIn predicate on the "refund_at" field. +func RefundAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundAt, vs...)) +} + +// RefundAtGT applies the GT predicate on the "refund_at" field. +func RefundAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundAt, v)) +} + +// RefundAtGTE applies the GTE predicate on the "refund_at" field. +func RefundAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundAt, v)) +} + +// RefundAtLT applies the LT predicate on the "refund_at" field. +func RefundAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundAt, v)) +} + +// RefundAtLTE applies the LTE predicate on the "refund_at" field. +func RefundAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundAt, v)) +} + +// RefundAtIsNil applies the IsNil predicate on the "refund_at" field. +func RefundAtIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundAt)) +} + +// RefundAtNotNil applies the NotNil predicate on the "refund_at" field. +func RefundAtNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundAt)) +} + +// ForceRefundEQ applies the EQ predicate on the "force_refund" field. +func ForceRefundEQ(v bool) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldForceRefund, v)) +} + +// ForceRefundNEQ applies the NEQ predicate on the "force_refund" field. +func ForceRefundNEQ(v bool) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldForceRefund, v)) +} + +// RefundRequestedAtEQ applies the EQ predicate on the "refund_requested_at" field. +func RefundRequestedAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedAt, v)) +} + +// RefundRequestedAtNEQ applies the NEQ predicate on the "refund_requested_at" field. +func RefundRequestedAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundRequestedAt, v)) +} + +// RefundRequestedAtIn applies the In predicate on the "refund_requested_at" field. +func RefundRequestedAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundRequestedAt, vs...)) +} + +// RefundRequestedAtNotIn applies the NotIn predicate on the "refund_requested_at" field. +func RefundRequestedAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundRequestedAt, vs...)) +} + +// RefundRequestedAtGT applies the GT predicate on the "refund_requested_at" field. +func RefundRequestedAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundRequestedAt, v)) +} + +// RefundRequestedAtGTE applies the GTE predicate on the "refund_requested_at" field. +func RefundRequestedAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundRequestedAt, v)) +} + +// RefundRequestedAtLT applies the LT predicate on the "refund_requested_at" field. +func RefundRequestedAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundRequestedAt, v)) +} + +// RefundRequestedAtLTE applies the LTE predicate on the "refund_requested_at" field. +func RefundRequestedAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundRequestedAt, v)) +} + +// RefundRequestedAtIsNil applies the IsNil predicate on the "refund_requested_at" field. +func RefundRequestedAtIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundRequestedAt)) +} + +// RefundRequestedAtNotNil applies the NotNil predicate on the "refund_requested_at" field. +func RefundRequestedAtNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundRequestedAt)) +} + +// RefundRequestReasonEQ applies the EQ predicate on the "refund_request_reason" field. +func RefundRequestReasonEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonNEQ applies the NEQ predicate on the "refund_request_reason" field. +func RefundRequestReasonNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonIn applies the In predicate on the "refund_request_reason" field. +func RefundRequestReasonIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundRequestReason, vs...)) +} + +// RefundRequestReasonNotIn applies the NotIn predicate on the "refund_request_reason" field. +func RefundRequestReasonNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundRequestReason, vs...)) +} + +// RefundRequestReasonGT applies the GT predicate on the "refund_request_reason" field. +func RefundRequestReasonGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonGTE applies the GTE predicate on the "refund_request_reason" field. +func RefundRequestReasonGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonLT applies the LT predicate on the "refund_request_reason" field. +func RefundRequestReasonLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonLTE applies the LTE predicate on the "refund_request_reason" field. +func RefundRequestReasonLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonContains applies the Contains predicate on the "refund_request_reason" field. +func RefundRequestReasonContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonHasPrefix applies the HasPrefix predicate on the "refund_request_reason" field. +func RefundRequestReasonHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonHasSuffix applies the HasSuffix predicate on the "refund_request_reason" field. +func RefundRequestReasonHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonIsNil applies the IsNil predicate on the "refund_request_reason" field. +func RefundRequestReasonIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundRequestReason)) +} + +// RefundRequestReasonNotNil applies the NotNil predicate on the "refund_request_reason" field. +func RefundRequestReasonNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundRequestReason)) +} + +// RefundRequestReasonEqualFold applies the EqualFold predicate on the "refund_request_reason" field. +func RefundRequestReasonEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundRequestReason, v)) +} + +// RefundRequestReasonContainsFold applies the ContainsFold predicate on the "refund_request_reason" field. +func RefundRequestReasonContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundRequestReason, v)) +} + +// RefundRequestedByEQ applies the EQ predicate on the "refund_requested_by" field. +func RefundRequestedByEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByNEQ applies the NEQ predicate on the "refund_requested_by" field. +func RefundRequestedByNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByIn applies the In predicate on the "refund_requested_by" field. +func RefundRequestedByIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundRequestedBy, vs...)) +} + +// RefundRequestedByNotIn applies the NotIn predicate on the "refund_requested_by" field. +func RefundRequestedByNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundRequestedBy, vs...)) +} + +// RefundRequestedByGT applies the GT predicate on the "refund_requested_by" field. +func RefundRequestedByGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByGTE applies the GTE predicate on the "refund_requested_by" field. +func RefundRequestedByGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByLT applies the LT predicate on the "refund_requested_by" field. +func RefundRequestedByLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByLTE applies the LTE predicate on the "refund_requested_by" field. +func RefundRequestedByLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByContains applies the Contains predicate on the "refund_requested_by" field. +func RefundRequestedByContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByHasPrefix applies the HasPrefix predicate on the "refund_requested_by" field. +func RefundRequestedByHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByHasSuffix applies the HasSuffix predicate on the "refund_requested_by" field. +func RefundRequestedByHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByIsNil applies the IsNil predicate on the "refund_requested_by" field. +func RefundRequestedByIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundRequestedBy)) +} + +// RefundRequestedByNotNil applies the NotNil predicate on the "refund_requested_by" field. +func RefundRequestedByNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundRequestedBy)) +} + +// RefundRequestedByEqualFold applies the EqualFold predicate on the "refund_requested_by" field. +func RefundRequestedByEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundRequestedBy, v)) +} + +// RefundRequestedByContainsFold applies the ContainsFold predicate on the "refund_requested_by" field. +func RefundRequestedByContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundRequestedBy, v)) +} + +// ExpiresAtEQ applies the EQ predicate on the "expires_at" field. +func ExpiresAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldExpiresAt, v)) +} + +// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. +func ExpiresAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldExpiresAt, v)) +} + +// ExpiresAtIn applies the In predicate on the "expires_at" field. +func ExpiresAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. +func ExpiresAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtGT applies the GT predicate on the "expires_at" field. +func ExpiresAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldExpiresAt, v)) +} + +// ExpiresAtGTE applies the GTE predicate on the "expires_at" field. +func ExpiresAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldExpiresAt, v)) +} + +// ExpiresAtLT applies the LT predicate on the "expires_at" field. +func ExpiresAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldExpiresAt, v)) +} + +// ExpiresAtLTE applies the LTE predicate on the "expires_at" field. +func ExpiresAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldExpiresAt, v)) +} + +// PaidAtEQ applies the EQ predicate on the "paid_at" field. +func PaidAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldPaidAt, v)) +} + +// PaidAtNEQ applies the NEQ predicate on the "paid_at" field. +func PaidAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldPaidAt, v)) +} + +// PaidAtIn applies the In predicate on the "paid_at" field. +func PaidAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldPaidAt, vs...)) +} + +// PaidAtNotIn applies the NotIn predicate on the "paid_at" field. +func PaidAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldPaidAt, vs...)) +} + +// PaidAtGT applies the GT predicate on the "paid_at" field. +func PaidAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldPaidAt, v)) +} + +// PaidAtGTE applies the GTE predicate on the "paid_at" field. +func PaidAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldPaidAt, v)) +} + +// PaidAtLT applies the LT predicate on the "paid_at" field. +func PaidAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldPaidAt, v)) +} + +// PaidAtLTE applies the LTE predicate on the "paid_at" field. +func PaidAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldPaidAt, v)) +} + +// PaidAtIsNil applies the IsNil predicate on the "paid_at" field. +func PaidAtIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldPaidAt)) +} + +// PaidAtNotNil applies the NotNil predicate on the "paid_at" field. +func PaidAtNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldPaidAt)) +} + +// CompletedAtEQ applies the EQ predicate on the "completed_at" field. +func CompletedAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldCompletedAt, v)) +} + +// CompletedAtNEQ applies the NEQ predicate on the "completed_at" field. +func CompletedAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldCompletedAt, v)) +} + +// CompletedAtIn applies the In predicate on the "completed_at" field. +func CompletedAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldCompletedAt, vs...)) +} + +// CompletedAtNotIn applies the NotIn predicate on the "completed_at" field. +func CompletedAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldCompletedAt, vs...)) +} + +// CompletedAtGT applies the GT predicate on the "completed_at" field. +func CompletedAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldCompletedAt, v)) +} + +// CompletedAtGTE applies the GTE predicate on the "completed_at" field. +func CompletedAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldCompletedAt, v)) +} + +// CompletedAtLT applies the LT predicate on the "completed_at" field. +func CompletedAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldCompletedAt, v)) +} + +// CompletedAtLTE applies the LTE predicate on the "completed_at" field. +func CompletedAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldCompletedAt, v)) +} + +// CompletedAtIsNil applies the IsNil predicate on the "completed_at" field. +func CompletedAtIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldCompletedAt)) +} + +// CompletedAtNotNil applies the NotNil predicate on the "completed_at" field. +func CompletedAtNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldCompletedAt)) +} + +// FailedAtEQ applies the EQ predicate on the "failed_at" field. +func FailedAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldFailedAt, v)) +} + +// FailedAtNEQ applies the NEQ predicate on the "failed_at" field. +func FailedAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldFailedAt, v)) +} + +// FailedAtIn applies the In predicate on the "failed_at" field. +func FailedAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldFailedAt, vs...)) +} + +// FailedAtNotIn applies the NotIn predicate on the "failed_at" field. +func FailedAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldFailedAt, vs...)) +} + +// FailedAtGT applies the GT predicate on the "failed_at" field. +func FailedAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldFailedAt, v)) +} + +// FailedAtGTE applies the GTE predicate on the "failed_at" field. +func FailedAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldFailedAt, v)) +} + +// FailedAtLT applies the LT predicate on the "failed_at" field. +func FailedAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldFailedAt, v)) +} + +// FailedAtLTE applies the LTE predicate on the "failed_at" field. +func FailedAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldFailedAt, v)) +} + +// FailedAtIsNil applies the IsNil predicate on the "failed_at" field. +func FailedAtIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldFailedAt)) +} + +// FailedAtNotNil applies the NotNil predicate on the "failed_at" field. +func FailedAtNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldFailedAt)) +} + +// FailedReasonEQ applies the EQ predicate on the "failed_reason" field. +func FailedReasonEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldFailedReason, v)) +} + +// FailedReasonNEQ applies the NEQ predicate on the "failed_reason" field. +func FailedReasonNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldFailedReason, v)) +} + +// FailedReasonIn applies the In predicate on the "failed_reason" field. +func FailedReasonIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldFailedReason, vs...)) +} + +// FailedReasonNotIn applies the NotIn predicate on the "failed_reason" field. +func FailedReasonNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldFailedReason, vs...)) +} + +// FailedReasonGT applies the GT predicate on the "failed_reason" field. +func FailedReasonGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldFailedReason, v)) +} + +// FailedReasonGTE applies the GTE predicate on the "failed_reason" field. +func FailedReasonGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldFailedReason, v)) +} + +// FailedReasonLT applies the LT predicate on the "failed_reason" field. +func FailedReasonLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldFailedReason, v)) +} + +// FailedReasonLTE applies the LTE predicate on the "failed_reason" field. +func FailedReasonLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldFailedReason, v)) +} + +// FailedReasonContains applies the Contains predicate on the "failed_reason" field. +func FailedReasonContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldFailedReason, v)) +} + +// FailedReasonHasPrefix applies the HasPrefix predicate on the "failed_reason" field. +func FailedReasonHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldFailedReason, v)) +} + +// FailedReasonHasSuffix applies the HasSuffix predicate on the "failed_reason" field. +func FailedReasonHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldFailedReason, v)) +} + +// FailedReasonIsNil applies the IsNil predicate on the "failed_reason" field. +func FailedReasonIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldFailedReason)) +} + +// FailedReasonNotNil applies the NotNil predicate on the "failed_reason" field. +func FailedReasonNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldFailedReason)) +} + +// FailedReasonEqualFold applies the EqualFold predicate on the "failed_reason" field. +func FailedReasonEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldFailedReason, v)) +} + +// FailedReasonContainsFold applies the ContainsFold predicate on the "failed_reason" field. +func FailedReasonContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldFailedReason, v)) +} + +// ClientIPEQ applies the EQ predicate on the "client_ip" field. +func ClientIPEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldClientIP, v)) +} + +// ClientIPNEQ applies the NEQ predicate on the "client_ip" field. +func ClientIPNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldClientIP, v)) +} + +// ClientIPIn applies the In predicate on the "client_ip" field. +func ClientIPIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldClientIP, vs...)) +} + +// ClientIPNotIn applies the NotIn predicate on the "client_ip" field. +func ClientIPNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldClientIP, vs...)) +} + +// ClientIPGT applies the GT predicate on the "client_ip" field. +func ClientIPGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldClientIP, v)) +} + +// ClientIPGTE applies the GTE predicate on the "client_ip" field. +func ClientIPGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldClientIP, v)) +} + +// ClientIPLT applies the LT predicate on the "client_ip" field. +func ClientIPLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldClientIP, v)) +} + +// ClientIPLTE applies the LTE predicate on the "client_ip" field. +func ClientIPLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldClientIP, v)) +} + +// ClientIPContains applies the Contains predicate on the "client_ip" field. +func ClientIPContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldClientIP, v)) +} + +// ClientIPHasPrefix applies the HasPrefix predicate on the "client_ip" field. +func ClientIPHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldClientIP, v)) +} + +// ClientIPHasSuffix applies the HasSuffix predicate on the "client_ip" field. +func ClientIPHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldClientIP, v)) +} + +// ClientIPEqualFold applies the EqualFold predicate on the "client_ip" field. +func ClientIPEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldClientIP, v)) +} + +// ClientIPContainsFold applies the ContainsFold predicate on the "client_ip" field. +func ClientIPContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldClientIP, v)) +} + +// SrcHostEQ applies the EQ predicate on the "src_host" field. +func SrcHostEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSrcHost, v)) +} + +// SrcHostNEQ applies the NEQ predicate on the "src_host" field. +func SrcHostNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldSrcHost, v)) +} + +// SrcHostIn applies the In predicate on the "src_host" field. +func SrcHostIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldSrcHost, vs...)) +} + +// SrcHostNotIn applies the NotIn predicate on the "src_host" field. +func SrcHostNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldSrcHost, vs...)) +} + +// SrcHostGT applies the GT predicate on the "src_host" field. +func SrcHostGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldSrcHost, v)) +} + +// SrcHostGTE applies the GTE predicate on the "src_host" field. +func SrcHostGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldSrcHost, v)) +} + +// SrcHostLT applies the LT predicate on the "src_host" field. +func SrcHostLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldSrcHost, v)) +} + +// SrcHostLTE applies the LTE predicate on the "src_host" field. +func SrcHostLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldSrcHost, v)) +} + +// SrcHostContains applies the Contains predicate on the "src_host" field. +func SrcHostContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldSrcHost, v)) +} + +// SrcHostHasPrefix applies the HasPrefix predicate on the "src_host" field. +func SrcHostHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldSrcHost, v)) +} + +// SrcHostHasSuffix applies the HasSuffix predicate on the "src_host" field. +func SrcHostHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldSrcHost, v)) +} + +// SrcHostEqualFold applies the EqualFold predicate on the "src_host" field. +func SrcHostEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldSrcHost, v)) +} + +// SrcHostContainsFold applies the ContainsFold predicate on the "src_host" field. +func SrcHostContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldSrcHost, v)) +} + +// SrcURLEQ applies the EQ predicate on the "src_url" field. +func SrcURLEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldSrcURL, v)) +} + +// SrcURLNEQ applies the NEQ predicate on the "src_url" field. +func SrcURLNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldSrcURL, v)) +} + +// SrcURLIn applies the In predicate on the "src_url" field. +func SrcURLIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldSrcURL, vs...)) +} + +// SrcURLNotIn applies the NotIn predicate on the "src_url" field. +func SrcURLNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldSrcURL, vs...)) +} + +// SrcURLGT applies the GT predicate on the "src_url" field. +func SrcURLGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldSrcURL, v)) +} + +// SrcURLGTE applies the GTE predicate on the "src_url" field. +func SrcURLGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldSrcURL, v)) +} + +// SrcURLLT applies the LT predicate on the "src_url" field. +func SrcURLLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldSrcURL, v)) +} + +// SrcURLLTE applies the LTE predicate on the "src_url" field. +func SrcURLLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldSrcURL, v)) +} + +// SrcURLContains applies the Contains predicate on the "src_url" field. +func SrcURLContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldSrcURL, v)) +} + +// SrcURLHasPrefix applies the HasPrefix predicate on the "src_url" field. +func SrcURLHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldSrcURL, v)) +} + +// SrcURLHasSuffix applies the HasSuffix predicate on the "src_url" field. +func SrcURLHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldSrcURL, v)) +} + +// SrcURLIsNil applies the IsNil predicate on the "src_url" field. +func SrcURLIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldSrcURL)) +} + +// SrcURLNotNil applies the NotNil predicate on the "src_url" field. +func SrcURLNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldSrcURL)) +} + +// SrcURLEqualFold applies the EqualFold predicate on the "src_url" field. +func SrcURLEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldSrcURL, v)) +} + +// SrcURLContainsFold applies the ContainsFold predicate on the "src_url" field. +func SrcURLContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldSrcURL, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.PaymentOrder { + return predicate.PaymentOrder(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.PaymentOrder { + return predicate.PaymentOrder(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.PaymentOrder) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.PaymentOrder) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.PaymentOrder) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.NotPredicates(p)) +} diff --git a/backend/ent/paymentorder_create.go b/backend/ent/paymentorder_create.go new file mode 100644 index 0000000000000000000000000000000000000000..030983390124b2840304ae5f021b6634ed3abdba --- /dev/null +++ b/backend/ent/paymentorder_create.go @@ -0,0 +1,3109 @@ +// 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/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/user" +) + +// PaymentOrderCreate is the builder for creating a PaymentOrder entity. +type PaymentOrderCreate struct { + config + mutation *PaymentOrderMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetUserID sets the "user_id" field. +func (_c *PaymentOrderCreate) SetUserID(v int64) *PaymentOrderCreate { + _c.mutation.SetUserID(v) + return _c +} + +// SetUserEmail sets the "user_email" field. +func (_c *PaymentOrderCreate) SetUserEmail(v string) *PaymentOrderCreate { + _c.mutation.SetUserEmail(v) + return _c +} + +// SetUserName sets the "user_name" field. +func (_c *PaymentOrderCreate) SetUserName(v string) *PaymentOrderCreate { + _c.mutation.SetUserName(v) + return _c +} + +// SetUserNotes sets the "user_notes" field. +func (_c *PaymentOrderCreate) SetUserNotes(v string) *PaymentOrderCreate { + _c.mutation.SetUserNotes(v) + return _c +} + +// SetNillableUserNotes sets the "user_notes" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableUserNotes(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetUserNotes(*v) + } + return _c +} + +// SetAmount sets the "amount" field. +func (_c *PaymentOrderCreate) SetAmount(v float64) *PaymentOrderCreate { + _c.mutation.SetAmount(v) + return _c +} + +// SetPayAmount sets the "pay_amount" field. +func (_c *PaymentOrderCreate) SetPayAmount(v float64) *PaymentOrderCreate { + _c.mutation.SetPayAmount(v) + return _c +} + +// SetFeeRate sets the "fee_rate" field. +func (_c *PaymentOrderCreate) SetFeeRate(v float64) *PaymentOrderCreate { + _c.mutation.SetFeeRate(v) + return _c +} + +// SetNillableFeeRate sets the "fee_rate" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableFeeRate(v *float64) *PaymentOrderCreate { + if v != nil { + _c.SetFeeRate(*v) + } + return _c +} + +// SetRechargeCode sets the "recharge_code" field. +func (_c *PaymentOrderCreate) SetRechargeCode(v string) *PaymentOrderCreate { + _c.mutation.SetRechargeCode(v) + return _c +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (_c *PaymentOrderCreate) SetOutTradeNo(v string) *PaymentOrderCreate { + _c.mutation.SetOutTradeNo(v) + return _c +} + +// SetNillableOutTradeNo sets the "out_trade_no" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableOutTradeNo(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetOutTradeNo(*v) + } + return _c +} + +// SetPaymentType sets the "payment_type" field. +func (_c *PaymentOrderCreate) SetPaymentType(v string) *PaymentOrderCreate { + _c.mutation.SetPaymentType(v) + return _c +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (_c *PaymentOrderCreate) SetPaymentTradeNo(v string) *PaymentOrderCreate { + _c.mutation.SetPaymentTradeNo(v) + return _c +} + +// SetPayURL sets the "pay_url" field. +func (_c *PaymentOrderCreate) SetPayURL(v string) *PaymentOrderCreate { + _c.mutation.SetPayURL(v) + return _c +} + +// SetNillablePayURL sets the "pay_url" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillablePayURL(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetPayURL(*v) + } + return _c +} + +// SetQrCode sets the "qr_code" field. +func (_c *PaymentOrderCreate) SetQrCode(v string) *PaymentOrderCreate { + _c.mutation.SetQrCode(v) + return _c +} + +// SetNillableQrCode sets the "qr_code" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableQrCode(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetQrCode(*v) + } + return _c +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (_c *PaymentOrderCreate) SetQrCodeImg(v string) *PaymentOrderCreate { + _c.mutation.SetQrCodeImg(v) + return _c +} + +// SetNillableQrCodeImg sets the "qr_code_img" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableQrCodeImg(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetQrCodeImg(*v) + } + return _c +} + +// SetOrderType sets the "order_type" field. +func (_c *PaymentOrderCreate) SetOrderType(v string) *PaymentOrderCreate { + _c.mutation.SetOrderType(v) + return _c +} + +// SetNillableOrderType sets the "order_type" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableOrderType(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetOrderType(*v) + } + return _c +} + +// SetPlanID sets the "plan_id" field. +func (_c *PaymentOrderCreate) SetPlanID(v int64) *PaymentOrderCreate { + _c.mutation.SetPlanID(v) + return _c +} + +// SetNillablePlanID sets the "plan_id" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillablePlanID(v *int64) *PaymentOrderCreate { + if v != nil { + _c.SetPlanID(*v) + } + return _c +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (_c *PaymentOrderCreate) SetSubscriptionGroupID(v int64) *PaymentOrderCreate { + _c.mutation.SetSubscriptionGroupID(v) + return _c +} + +// SetNillableSubscriptionGroupID sets the "subscription_group_id" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableSubscriptionGroupID(v *int64) *PaymentOrderCreate { + if v != nil { + _c.SetSubscriptionGroupID(*v) + } + return _c +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (_c *PaymentOrderCreate) SetSubscriptionDays(v int) *PaymentOrderCreate { + _c.mutation.SetSubscriptionDays(v) + return _c +} + +// SetNillableSubscriptionDays sets the "subscription_days" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableSubscriptionDays(v *int) *PaymentOrderCreate { + if v != nil { + _c.SetSubscriptionDays(*v) + } + return _c +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (_c *PaymentOrderCreate) SetProviderInstanceID(v string) *PaymentOrderCreate { + _c.mutation.SetProviderInstanceID(v) + return _c +} + +// SetNillableProviderInstanceID sets the "provider_instance_id" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableProviderInstanceID(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetProviderInstanceID(*v) + } + return _c +} + +// SetStatus sets the "status" field. +func (_c *PaymentOrderCreate) SetStatus(v string) *PaymentOrderCreate { + _c.mutation.SetStatus(v) + return _c +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableStatus(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetStatus(*v) + } + return _c +} + +// SetRefundAmount sets the "refund_amount" field. +func (_c *PaymentOrderCreate) SetRefundAmount(v float64) *PaymentOrderCreate { + _c.mutation.SetRefundAmount(v) + return _c +} + +// SetNillableRefundAmount sets the "refund_amount" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundAmount(v *float64) *PaymentOrderCreate { + if v != nil { + _c.SetRefundAmount(*v) + } + return _c +} + +// SetRefundReason sets the "refund_reason" field. +func (_c *PaymentOrderCreate) SetRefundReason(v string) *PaymentOrderCreate { + _c.mutation.SetRefundReason(v) + return _c +} + +// SetNillableRefundReason sets the "refund_reason" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundReason(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetRefundReason(*v) + } + return _c +} + +// SetRefundAt sets the "refund_at" field. +func (_c *PaymentOrderCreate) SetRefundAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetRefundAt(v) + return _c +} + +// SetNillableRefundAt sets the "refund_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetRefundAt(*v) + } + return _c +} + +// SetForceRefund sets the "force_refund" field. +func (_c *PaymentOrderCreate) SetForceRefund(v bool) *PaymentOrderCreate { + _c.mutation.SetForceRefund(v) + return _c +} + +// SetNillableForceRefund sets the "force_refund" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableForceRefund(v *bool) *PaymentOrderCreate { + if v != nil { + _c.SetForceRefund(*v) + } + return _c +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (_c *PaymentOrderCreate) SetRefundRequestedAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetRefundRequestedAt(v) + return _c +} + +// SetNillableRefundRequestedAt sets the "refund_requested_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundRequestedAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetRefundRequestedAt(*v) + } + return _c +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (_c *PaymentOrderCreate) SetRefundRequestReason(v string) *PaymentOrderCreate { + _c.mutation.SetRefundRequestReason(v) + return _c +} + +// SetNillableRefundRequestReason sets the "refund_request_reason" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundRequestReason(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetRefundRequestReason(*v) + } + return _c +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (_c *PaymentOrderCreate) SetRefundRequestedBy(v string) *PaymentOrderCreate { + _c.mutation.SetRefundRequestedBy(v) + return _c +} + +// SetNillableRefundRequestedBy sets the "refund_requested_by" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundRequestedBy(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetRefundRequestedBy(*v) + } + return _c +} + +// SetExpiresAt sets the "expires_at" field. +func (_c *PaymentOrderCreate) SetExpiresAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetExpiresAt(v) + return _c +} + +// SetPaidAt sets the "paid_at" field. +func (_c *PaymentOrderCreate) SetPaidAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetPaidAt(v) + return _c +} + +// SetNillablePaidAt sets the "paid_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillablePaidAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetPaidAt(*v) + } + return _c +} + +// SetCompletedAt sets the "completed_at" field. +func (_c *PaymentOrderCreate) SetCompletedAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetCompletedAt(v) + return _c +} + +// SetNillableCompletedAt sets the "completed_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableCompletedAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetCompletedAt(*v) + } + return _c +} + +// SetFailedAt sets the "failed_at" field. +func (_c *PaymentOrderCreate) SetFailedAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetFailedAt(v) + return _c +} + +// SetNillableFailedAt sets the "failed_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableFailedAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetFailedAt(*v) + } + return _c +} + +// SetFailedReason sets the "failed_reason" field. +func (_c *PaymentOrderCreate) SetFailedReason(v string) *PaymentOrderCreate { + _c.mutation.SetFailedReason(v) + return _c +} + +// SetNillableFailedReason sets the "failed_reason" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableFailedReason(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetFailedReason(*v) + } + return _c +} + +// SetClientIP sets the "client_ip" field. +func (_c *PaymentOrderCreate) SetClientIP(v string) *PaymentOrderCreate { + _c.mutation.SetClientIP(v) + return _c +} + +// SetSrcHost sets the "src_host" field. +func (_c *PaymentOrderCreate) SetSrcHost(v string) *PaymentOrderCreate { + _c.mutation.SetSrcHost(v) + return _c +} + +// SetSrcURL sets the "src_url" field. +func (_c *PaymentOrderCreate) SetSrcURL(v string) *PaymentOrderCreate { + _c.mutation.SetSrcURL(v) + return _c +} + +// SetNillableSrcURL sets the "src_url" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableSrcURL(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetSrcURL(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *PaymentOrderCreate) SetCreatedAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableCreatedAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *PaymentOrderCreate) SetUpdatedAt(v time.Time) *PaymentOrderCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableUpdatedAt(v *time.Time) *PaymentOrderCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetUser sets the "user" edge to the User entity. +func (_c *PaymentOrderCreate) SetUser(v *User) *PaymentOrderCreate { + return _c.SetUserID(v.ID) +} + +// Mutation returns the PaymentOrderMutation object of the builder. +func (_c *PaymentOrderCreate) Mutation() *PaymentOrderMutation { + return _c.mutation +} + +// Save creates the PaymentOrder in the database. +func (_c *PaymentOrderCreate) Save(ctx context.Context) (*PaymentOrder, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *PaymentOrderCreate) SaveX(ctx context.Context) *PaymentOrder { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *PaymentOrderCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *PaymentOrderCreate) 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 *PaymentOrderCreate) defaults() { + if _, ok := _c.mutation.FeeRate(); !ok { + v := paymentorder.DefaultFeeRate + _c.mutation.SetFeeRate(v) + } + if _, ok := _c.mutation.OutTradeNo(); !ok { + v := paymentorder.DefaultOutTradeNo + _c.mutation.SetOutTradeNo(v) + } + if _, ok := _c.mutation.OrderType(); !ok { + v := paymentorder.DefaultOrderType + _c.mutation.SetOrderType(v) + } + if _, ok := _c.mutation.Status(); !ok { + v := paymentorder.DefaultStatus + _c.mutation.SetStatus(v) + } + if _, ok := _c.mutation.RefundAmount(); !ok { + v := paymentorder.DefaultRefundAmount + _c.mutation.SetRefundAmount(v) + } + if _, ok := _c.mutation.ForceRefund(); !ok { + v := paymentorder.DefaultForceRefund + _c.mutation.SetForceRefund(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := paymentorder.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := paymentorder.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *PaymentOrderCreate) check() error { + if _, ok := _c.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "PaymentOrder.user_id"`)} + } + if _, ok := _c.mutation.UserEmail(); !ok { + return &ValidationError{Name: "user_email", err: errors.New(`ent: missing required field "PaymentOrder.user_email"`)} + } + if v, ok := _c.mutation.UserEmail(); ok { + if err := paymentorder.UserEmailValidator(v); err != nil { + return &ValidationError{Name: "user_email", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_email": %w`, err)} + } + } + if _, ok := _c.mutation.UserName(); !ok { + return &ValidationError{Name: "user_name", err: errors.New(`ent: missing required field "PaymentOrder.user_name"`)} + } + if v, ok := _c.mutation.UserName(); ok { + if err := paymentorder.UserNameValidator(v); err != nil { + return &ValidationError{Name: "user_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_name": %w`, err)} + } + } + if _, ok := _c.mutation.Amount(); !ok { + return &ValidationError{Name: "amount", err: errors.New(`ent: missing required field "PaymentOrder.amount"`)} + } + if _, ok := _c.mutation.PayAmount(); !ok { + return &ValidationError{Name: "pay_amount", err: errors.New(`ent: missing required field "PaymentOrder.pay_amount"`)} + } + if _, ok := _c.mutation.FeeRate(); !ok { + return &ValidationError{Name: "fee_rate", err: errors.New(`ent: missing required field "PaymentOrder.fee_rate"`)} + } + if _, ok := _c.mutation.RechargeCode(); !ok { + return &ValidationError{Name: "recharge_code", err: errors.New(`ent: missing required field "PaymentOrder.recharge_code"`)} + } + if v, ok := _c.mutation.RechargeCode(); ok { + if err := paymentorder.RechargeCodeValidator(v); err != nil { + return &ValidationError{Name: "recharge_code", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.recharge_code": %w`, err)} + } + } + if _, ok := _c.mutation.OutTradeNo(); !ok { + return &ValidationError{Name: "out_trade_no", err: errors.New(`ent: missing required field "PaymentOrder.out_trade_no"`)} + } + if v, ok := _c.mutation.OutTradeNo(); ok { + if err := paymentorder.OutTradeNoValidator(v); err != nil { + return &ValidationError{Name: "out_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.out_trade_no": %w`, err)} + } + } + if _, ok := _c.mutation.PaymentType(); !ok { + return &ValidationError{Name: "payment_type", err: errors.New(`ent: missing required field "PaymentOrder.payment_type"`)} + } + if v, ok := _c.mutation.PaymentType(); ok { + if err := paymentorder.PaymentTypeValidator(v); err != nil { + return &ValidationError{Name: "payment_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_type": %w`, err)} + } + } + if _, ok := _c.mutation.PaymentTradeNo(); !ok { + return &ValidationError{Name: "payment_trade_no", err: errors.New(`ent: missing required field "PaymentOrder.payment_trade_no"`)} + } + if v, ok := _c.mutation.PaymentTradeNo(); ok { + if err := paymentorder.PaymentTradeNoValidator(v); err != nil { + return &ValidationError{Name: "payment_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_trade_no": %w`, err)} + } + } + if _, ok := _c.mutation.OrderType(); !ok { + return &ValidationError{Name: "order_type", err: errors.New(`ent: missing required field "PaymentOrder.order_type"`)} + } + if v, ok := _c.mutation.OrderType(); ok { + if err := paymentorder.OrderTypeValidator(v); err != nil { + return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)} + } + } + if v, ok := _c.mutation.ProviderInstanceID(); ok { + if err := paymentorder.ProviderInstanceIDValidator(v); err != nil { + return &ValidationError{Name: "provider_instance_id", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.provider_instance_id": %w`, err)} + } + } + if _, ok := _c.mutation.Status(); !ok { + return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "PaymentOrder.status"`)} + } + if v, ok := _c.mutation.Status(); ok { + if err := paymentorder.StatusValidator(v); err != nil { + return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)} + } + } + if _, ok := _c.mutation.RefundAmount(); !ok { + return &ValidationError{Name: "refund_amount", err: errors.New(`ent: missing required field "PaymentOrder.refund_amount"`)} + } + if _, ok := _c.mutation.ForceRefund(); !ok { + return &ValidationError{Name: "force_refund", err: errors.New(`ent: missing required field "PaymentOrder.force_refund"`)} + } + if v, ok := _c.mutation.RefundRequestedBy(); ok { + if err := paymentorder.RefundRequestedByValidator(v); err != nil { + return &ValidationError{Name: "refund_requested_by", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_requested_by": %w`, err)} + } + } + if _, ok := _c.mutation.ExpiresAt(); !ok { + return &ValidationError{Name: "expires_at", err: errors.New(`ent: missing required field "PaymentOrder.expires_at"`)} + } + if _, ok := _c.mutation.ClientIP(); !ok { + return &ValidationError{Name: "client_ip", err: errors.New(`ent: missing required field "PaymentOrder.client_ip"`)} + } + if v, ok := _c.mutation.ClientIP(); ok { + if err := paymentorder.ClientIPValidator(v); err != nil { + return &ValidationError{Name: "client_ip", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.client_ip": %w`, err)} + } + } + if _, ok := _c.mutation.SrcHost(); !ok { + return &ValidationError{Name: "src_host", err: errors.New(`ent: missing required field "PaymentOrder.src_host"`)} + } + if v, ok := _c.mutation.SrcHost(); ok { + if err := paymentorder.SrcHostValidator(v); err != nil { + return &ValidationError{Name: "src_host", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.src_host": %w`, err)} + } + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentOrder.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "PaymentOrder.updated_at"`)} + } + if len(_c.mutation.UserIDs()) == 0 { + return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "PaymentOrder.user"`)} + } + return nil +} + +func (_c *PaymentOrderCreate) sqlSave(ctx context.Context) (*PaymentOrder, 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 *PaymentOrderCreate) createSpec() (*PaymentOrder, *sqlgraph.CreateSpec) { + var ( + _node = &PaymentOrder{config: _c.config} + _spec = sqlgraph.NewCreateSpec(paymentorder.Table, sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if value, ok := _c.mutation.UserEmail(); ok { + _spec.SetField(paymentorder.FieldUserEmail, field.TypeString, value) + _node.UserEmail = value + } + if value, ok := _c.mutation.UserName(); ok { + _spec.SetField(paymentorder.FieldUserName, field.TypeString, value) + _node.UserName = value + } + if value, ok := _c.mutation.UserNotes(); ok { + _spec.SetField(paymentorder.FieldUserNotes, field.TypeString, value) + _node.UserNotes = &value + } + if value, ok := _c.mutation.Amount(); ok { + _spec.SetField(paymentorder.FieldAmount, field.TypeFloat64, value) + _node.Amount = value + } + if value, ok := _c.mutation.PayAmount(); ok { + _spec.SetField(paymentorder.FieldPayAmount, field.TypeFloat64, value) + _node.PayAmount = value + } + if value, ok := _c.mutation.FeeRate(); ok { + _spec.SetField(paymentorder.FieldFeeRate, field.TypeFloat64, value) + _node.FeeRate = value + } + if value, ok := _c.mutation.RechargeCode(); ok { + _spec.SetField(paymentorder.FieldRechargeCode, field.TypeString, value) + _node.RechargeCode = value + } + if value, ok := _c.mutation.OutTradeNo(); ok { + _spec.SetField(paymentorder.FieldOutTradeNo, field.TypeString, value) + _node.OutTradeNo = value + } + if value, ok := _c.mutation.PaymentType(); ok { + _spec.SetField(paymentorder.FieldPaymentType, field.TypeString, value) + _node.PaymentType = value + } + if value, ok := _c.mutation.PaymentTradeNo(); ok { + _spec.SetField(paymentorder.FieldPaymentTradeNo, field.TypeString, value) + _node.PaymentTradeNo = value + } + if value, ok := _c.mutation.PayURL(); ok { + _spec.SetField(paymentorder.FieldPayURL, field.TypeString, value) + _node.PayURL = &value + } + if value, ok := _c.mutation.QrCode(); ok { + _spec.SetField(paymentorder.FieldQrCode, field.TypeString, value) + _node.QrCode = &value + } + if value, ok := _c.mutation.QrCodeImg(); ok { + _spec.SetField(paymentorder.FieldQrCodeImg, field.TypeString, value) + _node.QrCodeImg = &value + } + if value, ok := _c.mutation.OrderType(); ok { + _spec.SetField(paymentorder.FieldOrderType, field.TypeString, value) + _node.OrderType = value + } + if value, ok := _c.mutation.PlanID(); ok { + _spec.SetField(paymentorder.FieldPlanID, field.TypeInt64, value) + _node.PlanID = &value + } + if value, ok := _c.mutation.SubscriptionGroupID(); ok { + _spec.SetField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value) + _node.SubscriptionGroupID = &value + } + if value, ok := _c.mutation.SubscriptionDays(); ok { + _spec.SetField(paymentorder.FieldSubscriptionDays, field.TypeInt, value) + _node.SubscriptionDays = &value + } + if value, ok := _c.mutation.ProviderInstanceID(); ok { + _spec.SetField(paymentorder.FieldProviderInstanceID, field.TypeString, value) + _node.ProviderInstanceID = &value + } + if value, ok := _c.mutation.Status(); ok { + _spec.SetField(paymentorder.FieldStatus, field.TypeString, value) + _node.Status = value + } + if value, ok := _c.mutation.RefundAmount(); ok { + _spec.SetField(paymentorder.FieldRefundAmount, field.TypeFloat64, value) + _node.RefundAmount = value + } + if value, ok := _c.mutation.RefundReason(); ok { + _spec.SetField(paymentorder.FieldRefundReason, field.TypeString, value) + _node.RefundReason = &value + } + if value, ok := _c.mutation.RefundAt(); ok { + _spec.SetField(paymentorder.FieldRefundAt, field.TypeTime, value) + _node.RefundAt = &value + } + if value, ok := _c.mutation.ForceRefund(); ok { + _spec.SetField(paymentorder.FieldForceRefund, field.TypeBool, value) + _node.ForceRefund = value + } + if value, ok := _c.mutation.RefundRequestedAt(); ok { + _spec.SetField(paymentorder.FieldRefundRequestedAt, field.TypeTime, value) + _node.RefundRequestedAt = &value + } + if value, ok := _c.mutation.RefundRequestReason(); ok { + _spec.SetField(paymentorder.FieldRefundRequestReason, field.TypeString, value) + _node.RefundRequestReason = &value + } + if value, ok := _c.mutation.RefundRequestedBy(); ok { + _spec.SetField(paymentorder.FieldRefundRequestedBy, field.TypeString, value) + _node.RefundRequestedBy = &value + } + if value, ok := _c.mutation.ExpiresAt(); ok { + _spec.SetField(paymentorder.FieldExpiresAt, field.TypeTime, value) + _node.ExpiresAt = value + } + if value, ok := _c.mutation.PaidAt(); ok { + _spec.SetField(paymentorder.FieldPaidAt, field.TypeTime, value) + _node.PaidAt = &value + } + if value, ok := _c.mutation.CompletedAt(); ok { + _spec.SetField(paymentorder.FieldCompletedAt, field.TypeTime, value) + _node.CompletedAt = &value + } + if value, ok := _c.mutation.FailedAt(); ok { + _spec.SetField(paymentorder.FieldFailedAt, field.TypeTime, value) + _node.FailedAt = &value + } + if value, ok := _c.mutation.FailedReason(); ok { + _spec.SetField(paymentorder.FieldFailedReason, field.TypeString, value) + _node.FailedReason = &value + } + if value, ok := _c.mutation.ClientIP(); ok { + _spec.SetField(paymentorder.FieldClientIP, field.TypeString, value) + _node.ClientIP = value + } + if value, ok := _c.mutation.SrcHost(); ok { + _spec.SetField(paymentorder.FieldSrcHost, field.TypeString, value) + _node.SrcHost = value + } + if value, ok := _c.mutation.SrcURL(); ok { + _spec.SetField(paymentorder.FieldSrcURL, field.TypeString, value) + _node.SrcURL = &value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(paymentorder.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(paymentorder.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if nodes := _c.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: paymentorder.UserTable, + Columns: []string{paymentorder.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.PaymentOrder.Create(). +// SetUserID(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.PaymentOrderUpsert) { +// SetUserID(v+v). +// }). +// Exec(ctx) +func (_c *PaymentOrderCreate) OnConflict(opts ...sql.ConflictOption) *PaymentOrderUpsertOne { + _c.conflict = opts + return &PaymentOrderUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.PaymentOrder.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *PaymentOrderCreate) OnConflictColumns(columns ...string) *PaymentOrderUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &PaymentOrderUpsertOne{ + create: _c, + } +} + +type ( + // PaymentOrderUpsertOne is the builder for "upsert"-ing + // one PaymentOrder node. + PaymentOrderUpsertOne struct { + create *PaymentOrderCreate + } + + // PaymentOrderUpsert is the "OnConflict" setter. + PaymentOrderUpsert struct { + *sql.UpdateSet + } +) + +// SetUserID sets the "user_id" field. +func (u *PaymentOrderUpsert) SetUserID(v int64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldUserID, v) + return u +} + +// UpdateUserID sets the "user_id" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateUserID() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldUserID) + return u +} + +// SetUserEmail sets the "user_email" field. +func (u *PaymentOrderUpsert) SetUserEmail(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldUserEmail, v) + return u +} + +// UpdateUserEmail sets the "user_email" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateUserEmail() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldUserEmail) + return u +} + +// SetUserName sets the "user_name" field. +func (u *PaymentOrderUpsert) SetUserName(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldUserName, v) + return u +} + +// UpdateUserName sets the "user_name" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateUserName() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldUserName) + return u +} + +// SetUserNotes sets the "user_notes" field. +func (u *PaymentOrderUpsert) SetUserNotes(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldUserNotes, v) + return u +} + +// UpdateUserNotes sets the "user_notes" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateUserNotes() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldUserNotes) + return u +} + +// ClearUserNotes clears the value of the "user_notes" field. +func (u *PaymentOrderUpsert) ClearUserNotes() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldUserNotes) + return u +} + +// SetAmount sets the "amount" field. +func (u *PaymentOrderUpsert) SetAmount(v float64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldAmount, v) + return u +} + +// UpdateAmount sets the "amount" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateAmount() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldAmount) + return u +} + +// AddAmount adds v to the "amount" field. +func (u *PaymentOrderUpsert) AddAmount(v float64) *PaymentOrderUpsert { + u.Add(paymentorder.FieldAmount, v) + return u +} + +// SetPayAmount sets the "pay_amount" field. +func (u *PaymentOrderUpsert) SetPayAmount(v float64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldPayAmount, v) + return u +} + +// UpdatePayAmount sets the "pay_amount" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdatePayAmount() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldPayAmount) + return u +} + +// AddPayAmount adds v to the "pay_amount" field. +func (u *PaymentOrderUpsert) AddPayAmount(v float64) *PaymentOrderUpsert { + u.Add(paymentorder.FieldPayAmount, v) + return u +} + +// SetFeeRate sets the "fee_rate" field. +func (u *PaymentOrderUpsert) SetFeeRate(v float64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldFeeRate, v) + return u +} + +// UpdateFeeRate sets the "fee_rate" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateFeeRate() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldFeeRate) + return u +} + +// AddFeeRate adds v to the "fee_rate" field. +func (u *PaymentOrderUpsert) AddFeeRate(v float64) *PaymentOrderUpsert { + u.Add(paymentorder.FieldFeeRate, v) + return u +} + +// SetRechargeCode sets the "recharge_code" field. +func (u *PaymentOrderUpsert) SetRechargeCode(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRechargeCode, v) + return u +} + +// UpdateRechargeCode sets the "recharge_code" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRechargeCode() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRechargeCode) + return u +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (u *PaymentOrderUpsert) SetOutTradeNo(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldOutTradeNo, v) + return u +} + +// UpdateOutTradeNo sets the "out_trade_no" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateOutTradeNo() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldOutTradeNo) + return u +} + +// SetPaymentType sets the "payment_type" field. +func (u *PaymentOrderUpsert) SetPaymentType(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldPaymentType, v) + return u +} + +// UpdatePaymentType sets the "payment_type" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdatePaymentType() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldPaymentType) + return u +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (u *PaymentOrderUpsert) SetPaymentTradeNo(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldPaymentTradeNo, v) + return u +} + +// UpdatePaymentTradeNo sets the "payment_trade_no" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdatePaymentTradeNo() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldPaymentTradeNo) + return u +} + +// SetPayURL sets the "pay_url" field. +func (u *PaymentOrderUpsert) SetPayURL(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldPayURL, v) + return u +} + +// UpdatePayURL sets the "pay_url" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdatePayURL() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldPayURL) + return u +} + +// ClearPayURL clears the value of the "pay_url" field. +func (u *PaymentOrderUpsert) ClearPayURL() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldPayURL) + return u +} + +// SetQrCode sets the "qr_code" field. +func (u *PaymentOrderUpsert) SetQrCode(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldQrCode, v) + return u +} + +// UpdateQrCode sets the "qr_code" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateQrCode() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldQrCode) + return u +} + +// ClearQrCode clears the value of the "qr_code" field. +func (u *PaymentOrderUpsert) ClearQrCode() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldQrCode) + return u +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (u *PaymentOrderUpsert) SetQrCodeImg(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldQrCodeImg, v) + return u +} + +// UpdateQrCodeImg sets the "qr_code_img" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateQrCodeImg() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldQrCodeImg) + return u +} + +// ClearQrCodeImg clears the value of the "qr_code_img" field. +func (u *PaymentOrderUpsert) ClearQrCodeImg() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldQrCodeImg) + return u +} + +// SetOrderType sets the "order_type" field. +func (u *PaymentOrderUpsert) SetOrderType(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldOrderType, v) + return u +} + +// UpdateOrderType sets the "order_type" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateOrderType() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldOrderType) + return u +} + +// SetPlanID sets the "plan_id" field. +func (u *PaymentOrderUpsert) SetPlanID(v int64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldPlanID, v) + return u +} + +// UpdatePlanID sets the "plan_id" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdatePlanID() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldPlanID) + return u +} + +// AddPlanID adds v to the "plan_id" field. +func (u *PaymentOrderUpsert) AddPlanID(v int64) *PaymentOrderUpsert { + u.Add(paymentorder.FieldPlanID, v) + return u +} + +// ClearPlanID clears the value of the "plan_id" field. +func (u *PaymentOrderUpsert) ClearPlanID() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldPlanID) + return u +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (u *PaymentOrderUpsert) SetSubscriptionGroupID(v int64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldSubscriptionGroupID, v) + return u +} + +// UpdateSubscriptionGroupID sets the "subscription_group_id" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateSubscriptionGroupID() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldSubscriptionGroupID) + return u +} + +// AddSubscriptionGroupID adds v to the "subscription_group_id" field. +func (u *PaymentOrderUpsert) AddSubscriptionGroupID(v int64) *PaymentOrderUpsert { + u.Add(paymentorder.FieldSubscriptionGroupID, v) + return u +} + +// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field. +func (u *PaymentOrderUpsert) ClearSubscriptionGroupID() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldSubscriptionGroupID) + return u +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (u *PaymentOrderUpsert) SetSubscriptionDays(v int) *PaymentOrderUpsert { + u.Set(paymentorder.FieldSubscriptionDays, v) + return u +} + +// UpdateSubscriptionDays sets the "subscription_days" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateSubscriptionDays() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldSubscriptionDays) + return u +} + +// AddSubscriptionDays adds v to the "subscription_days" field. +func (u *PaymentOrderUpsert) AddSubscriptionDays(v int) *PaymentOrderUpsert { + u.Add(paymentorder.FieldSubscriptionDays, v) + return u +} + +// ClearSubscriptionDays clears the value of the "subscription_days" field. +func (u *PaymentOrderUpsert) ClearSubscriptionDays() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldSubscriptionDays) + return u +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (u *PaymentOrderUpsert) SetProviderInstanceID(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldProviderInstanceID, v) + return u +} + +// UpdateProviderInstanceID sets the "provider_instance_id" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateProviderInstanceID() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldProviderInstanceID) + return u +} + +// ClearProviderInstanceID clears the value of the "provider_instance_id" field. +func (u *PaymentOrderUpsert) ClearProviderInstanceID() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldProviderInstanceID) + return u +} + +// SetStatus sets the "status" field. +func (u *PaymentOrderUpsert) SetStatus(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldStatus, v) + return u +} + +// UpdateStatus sets the "status" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateStatus() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldStatus) + return u +} + +// SetRefundAmount sets the "refund_amount" field. +func (u *PaymentOrderUpsert) SetRefundAmount(v float64) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundAmount, v) + return u +} + +// UpdateRefundAmount sets the "refund_amount" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundAmount() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundAmount) + return u +} + +// AddRefundAmount adds v to the "refund_amount" field. +func (u *PaymentOrderUpsert) AddRefundAmount(v float64) *PaymentOrderUpsert { + u.Add(paymentorder.FieldRefundAmount, v) + return u +} + +// SetRefundReason sets the "refund_reason" field. +func (u *PaymentOrderUpsert) SetRefundReason(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundReason, v) + return u +} + +// UpdateRefundReason sets the "refund_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundReason() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundReason) + return u +} + +// ClearRefundReason clears the value of the "refund_reason" field. +func (u *PaymentOrderUpsert) ClearRefundReason() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldRefundReason) + return u +} + +// SetRefundAt sets the "refund_at" field. +func (u *PaymentOrderUpsert) SetRefundAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundAt, v) + return u +} + +// UpdateRefundAt sets the "refund_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundAt) + return u +} + +// ClearRefundAt clears the value of the "refund_at" field. +func (u *PaymentOrderUpsert) ClearRefundAt() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldRefundAt) + return u +} + +// SetForceRefund sets the "force_refund" field. +func (u *PaymentOrderUpsert) SetForceRefund(v bool) *PaymentOrderUpsert { + u.Set(paymentorder.FieldForceRefund, v) + return u +} + +// UpdateForceRefund sets the "force_refund" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateForceRefund() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldForceRefund) + return u +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (u *PaymentOrderUpsert) SetRefundRequestedAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundRequestedAt, v) + return u +} + +// UpdateRefundRequestedAt sets the "refund_requested_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundRequestedAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundRequestedAt) + return u +} + +// ClearRefundRequestedAt clears the value of the "refund_requested_at" field. +func (u *PaymentOrderUpsert) ClearRefundRequestedAt() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldRefundRequestedAt) + return u +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (u *PaymentOrderUpsert) SetRefundRequestReason(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundRequestReason, v) + return u +} + +// UpdateRefundRequestReason sets the "refund_request_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundRequestReason() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundRequestReason) + return u +} + +// ClearRefundRequestReason clears the value of the "refund_request_reason" field. +func (u *PaymentOrderUpsert) ClearRefundRequestReason() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldRefundRequestReason) + return u +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (u *PaymentOrderUpsert) SetRefundRequestedBy(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundRequestedBy, v) + return u +} + +// UpdateRefundRequestedBy sets the "refund_requested_by" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundRequestedBy() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundRequestedBy) + return u +} + +// ClearRefundRequestedBy clears the value of the "refund_requested_by" field. +func (u *PaymentOrderUpsert) ClearRefundRequestedBy() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldRefundRequestedBy) + return u +} + +// SetExpiresAt sets the "expires_at" field. +func (u *PaymentOrderUpsert) SetExpiresAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldExpiresAt, v) + return u +} + +// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateExpiresAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldExpiresAt) + return u +} + +// SetPaidAt sets the "paid_at" field. +func (u *PaymentOrderUpsert) SetPaidAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldPaidAt, v) + return u +} + +// UpdatePaidAt sets the "paid_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdatePaidAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldPaidAt) + return u +} + +// ClearPaidAt clears the value of the "paid_at" field. +func (u *PaymentOrderUpsert) ClearPaidAt() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldPaidAt) + return u +} + +// SetCompletedAt sets the "completed_at" field. +func (u *PaymentOrderUpsert) SetCompletedAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldCompletedAt, v) + return u +} + +// UpdateCompletedAt sets the "completed_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateCompletedAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldCompletedAt) + return u +} + +// ClearCompletedAt clears the value of the "completed_at" field. +func (u *PaymentOrderUpsert) ClearCompletedAt() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldCompletedAt) + return u +} + +// SetFailedAt sets the "failed_at" field. +func (u *PaymentOrderUpsert) SetFailedAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldFailedAt, v) + return u +} + +// UpdateFailedAt sets the "failed_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateFailedAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldFailedAt) + return u +} + +// ClearFailedAt clears the value of the "failed_at" field. +func (u *PaymentOrderUpsert) ClearFailedAt() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldFailedAt) + return u +} + +// SetFailedReason sets the "failed_reason" field. +func (u *PaymentOrderUpsert) SetFailedReason(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldFailedReason, v) + return u +} + +// UpdateFailedReason sets the "failed_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateFailedReason() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldFailedReason) + return u +} + +// ClearFailedReason clears the value of the "failed_reason" field. +func (u *PaymentOrderUpsert) ClearFailedReason() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldFailedReason) + return u +} + +// SetClientIP sets the "client_ip" field. +func (u *PaymentOrderUpsert) SetClientIP(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldClientIP, v) + return u +} + +// UpdateClientIP sets the "client_ip" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateClientIP() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldClientIP) + return u +} + +// SetSrcHost sets the "src_host" field. +func (u *PaymentOrderUpsert) SetSrcHost(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldSrcHost, v) + return u +} + +// UpdateSrcHost sets the "src_host" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateSrcHost() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldSrcHost) + return u +} + +// SetSrcURL sets the "src_url" field. +func (u *PaymentOrderUpsert) SetSrcURL(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldSrcURL, v) + return u +} + +// UpdateSrcURL sets the "src_url" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateSrcURL() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldSrcURL) + return u +} + +// ClearSrcURL clears the value of the "src_url" field. +func (u *PaymentOrderUpsert) ClearSrcURL() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldSrcURL) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *PaymentOrderUpsert) SetUpdatedAt(v time.Time) *PaymentOrderUpsert { + u.Set(paymentorder.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateUpdatedAt() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.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.PaymentOrder.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *PaymentOrderUpsertOne) UpdateNewValues() *PaymentOrderUpsertOne { + 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(paymentorder.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.PaymentOrder.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *PaymentOrderUpsertOne) Ignore() *PaymentOrderUpsertOne { + 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 *PaymentOrderUpsertOne) DoNothing() *PaymentOrderUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the PaymentOrderCreate.OnConflict +// documentation for more info. +func (u *PaymentOrderUpsertOne) Update(set func(*PaymentOrderUpsert)) *PaymentOrderUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&PaymentOrderUpsert{UpdateSet: update}) + })) + return u +} + +// SetUserID sets the "user_id" field. +func (u *PaymentOrderUpsertOne) SetUserID(v int64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserID(v) + }) +} + +// UpdateUserID sets the "user_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateUserID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserID() + }) +} + +// SetUserEmail sets the "user_email" field. +func (u *PaymentOrderUpsertOne) SetUserEmail(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserEmail(v) + }) +} + +// UpdateUserEmail sets the "user_email" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateUserEmail() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserEmail() + }) +} + +// SetUserName sets the "user_name" field. +func (u *PaymentOrderUpsertOne) SetUserName(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserName(v) + }) +} + +// UpdateUserName sets the "user_name" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateUserName() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserName() + }) +} + +// SetUserNotes sets the "user_notes" field. +func (u *PaymentOrderUpsertOne) SetUserNotes(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserNotes(v) + }) +} + +// UpdateUserNotes sets the "user_notes" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateUserNotes() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserNotes() + }) +} + +// ClearUserNotes clears the value of the "user_notes" field. +func (u *PaymentOrderUpsertOne) ClearUserNotes() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearUserNotes() + }) +} + +// SetAmount sets the "amount" field. +func (u *PaymentOrderUpsertOne) SetAmount(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetAmount(v) + }) +} + +// AddAmount adds v to the "amount" field. +func (u *PaymentOrderUpsertOne) AddAmount(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddAmount(v) + }) +} + +// UpdateAmount sets the "amount" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateAmount() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateAmount() + }) +} + +// SetPayAmount sets the "pay_amount" field. +func (u *PaymentOrderUpsertOne) SetPayAmount(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPayAmount(v) + }) +} + +// AddPayAmount adds v to the "pay_amount" field. +func (u *PaymentOrderUpsertOne) AddPayAmount(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddPayAmount(v) + }) +} + +// UpdatePayAmount sets the "pay_amount" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdatePayAmount() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePayAmount() + }) +} + +// SetFeeRate sets the "fee_rate" field. +func (u *PaymentOrderUpsertOne) SetFeeRate(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetFeeRate(v) + }) +} + +// AddFeeRate adds v to the "fee_rate" field. +func (u *PaymentOrderUpsertOne) AddFeeRate(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddFeeRate(v) + }) +} + +// UpdateFeeRate sets the "fee_rate" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateFeeRate() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateFeeRate() + }) +} + +// SetRechargeCode sets the "recharge_code" field. +func (u *PaymentOrderUpsertOne) SetRechargeCode(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRechargeCode(v) + }) +} + +// UpdateRechargeCode sets the "recharge_code" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRechargeCode() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRechargeCode() + }) +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (u *PaymentOrderUpsertOne) SetOutTradeNo(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetOutTradeNo(v) + }) +} + +// UpdateOutTradeNo sets the "out_trade_no" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateOutTradeNo() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateOutTradeNo() + }) +} + +// SetPaymentType sets the "payment_type" field. +func (u *PaymentOrderUpsertOne) SetPaymentType(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPaymentType(v) + }) +} + +// UpdatePaymentType sets the "payment_type" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdatePaymentType() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePaymentType() + }) +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (u *PaymentOrderUpsertOne) SetPaymentTradeNo(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPaymentTradeNo(v) + }) +} + +// UpdatePaymentTradeNo sets the "payment_trade_no" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdatePaymentTradeNo() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePaymentTradeNo() + }) +} + +// SetPayURL sets the "pay_url" field. +func (u *PaymentOrderUpsertOne) SetPayURL(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPayURL(v) + }) +} + +// UpdatePayURL sets the "pay_url" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdatePayURL() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePayURL() + }) +} + +// ClearPayURL clears the value of the "pay_url" field. +func (u *PaymentOrderUpsertOne) ClearPayURL() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearPayURL() + }) +} + +// SetQrCode sets the "qr_code" field. +func (u *PaymentOrderUpsertOne) SetQrCode(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetQrCode(v) + }) +} + +// UpdateQrCode sets the "qr_code" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateQrCode() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateQrCode() + }) +} + +// ClearQrCode clears the value of the "qr_code" field. +func (u *PaymentOrderUpsertOne) ClearQrCode() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearQrCode() + }) +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (u *PaymentOrderUpsertOne) SetQrCodeImg(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetQrCodeImg(v) + }) +} + +// UpdateQrCodeImg sets the "qr_code_img" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateQrCodeImg() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateQrCodeImg() + }) +} + +// ClearQrCodeImg clears the value of the "qr_code_img" field. +func (u *PaymentOrderUpsertOne) ClearQrCodeImg() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearQrCodeImg() + }) +} + +// SetOrderType sets the "order_type" field. +func (u *PaymentOrderUpsertOne) SetOrderType(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetOrderType(v) + }) +} + +// UpdateOrderType sets the "order_type" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateOrderType() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateOrderType() + }) +} + +// SetPlanID sets the "plan_id" field. +func (u *PaymentOrderUpsertOne) SetPlanID(v int64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPlanID(v) + }) +} + +// AddPlanID adds v to the "plan_id" field. +func (u *PaymentOrderUpsertOne) AddPlanID(v int64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddPlanID(v) + }) +} + +// UpdatePlanID sets the "plan_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdatePlanID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePlanID() + }) +} + +// ClearPlanID clears the value of the "plan_id" field. +func (u *PaymentOrderUpsertOne) ClearPlanID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearPlanID() + }) +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (u *PaymentOrderUpsertOne) SetSubscriptionGroupID(v int64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSubscriptionGroupID(v) + }) +} + +// AddSubscriptionGroupID adds v to the "subscription_group_id" field. +func (u *PaymentOrderUpsertOne) AddSubscriptionGroupID(v int64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddSubscriptionGroupID(v) + }) +} + +// UpdateSubscriptionGroupID sets the "subscription_group_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateSubscriptionGroupID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSubscriptionGroupID() + }) +} + +// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field. +func (u *PaymentOrderUpsertOne) ClearSubscriptionGroupID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearSubscriptionGroupID() + }) +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (u *PaymentOrderUpsertOne) SetSubscriptionDays(v int) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSubscriptionDays(v) + }) +} + +// AddSubscriptionDays adds v to the "subscription_days" field. +func (u *PaymentOrderUpsertOne) AddSubscriptionDays(v int) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddSubscriptionDays(v) + }) +} + +// UpdateSubscriptionDays sets the "subscription_days" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateSubscriptionDays() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSubscriptionDays() + }) +} + +// ClearSubscriptionDays clears the value of the "subscription_days" field. +func (u *PaymentOrderUpsertOne) ClearSubscriptionDays() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearSubscriptionDays() + }) +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (u *PaymentOrderUpsertOne) SetProviderInstanceID(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetProviderInstanceID(v) + }) +} + +// UpdateProviderInstanceID sets the "provider_instance_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateProviderInstanceID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateProviderInstanceID() + }) +} + +// ClearProviderInstanceID clears the value of the "provider_instance_id" field. +func (u *PaymentOrderUpsertOne) ClearProviderInstanceID() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearProviderInstanceID() + }) +} + +// SetStatus sets the "status" field. +func (u *PaymentOrderUpsertOne) SetStatus(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetStatus(v) + }) +} + +// UpdateStatus sets the "status" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateStatus() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateStatus() + }) +} + +// SetRefundAmount sets the "refund_amount" field. +func (u *PaymentOrderUpsertOne) SetRefundAmount(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundAmount(v) + }) +} + +// AddRefundAmount adds v to the "refund_amount" field. +func (u *PaymentOrderUpsertOne) AddRefundAmount(v float64) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddRefundAmount(v) + }) +} + +// UpdateRefundAmount sets the "refund_amount" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundAmount() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundAmount() + }) +} + +// SetRefundReason sets the "refund_reason" field. +func (u *PaymentOrderUpsertOne) SetRefundReason(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundReason(v) + }) +} + +// UpdateRefundReason sets the "refund_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundReason() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundReason() + }) +} + +// ClearRefundReason clears the value of the "refund_reason" field. +func (u *PaymentOrderUpsertOne) ClearRefundReason() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundReason() + }) +} + +// SetRefundAt sets the "refund_at" field. +func (u *PaymentOrderUpsertOne) SetRefundAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundAt(v) + }) +} + +// UpdateRefundAt sets the "refund_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundAt() + }) +} + +// ClearRefundAt clears the value of the "refund_at" field. +func (u *PaymentOrderUpsertOne) ClearRefundAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundAt() + }) +} + +// SetForceRefund sets the "force_refund" field. +func (u *PaymentOrderUpsertOne) SetForceRefund(v bool) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetForceRefund(v) + }) +} + +// UpdateForceRefund sets the "force_refund" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateForceRefund() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateForceRefund() + }) +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (u *PaymentOrderUpsertOne) SetRefundRequestedAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundRequestedAt(v) + }) +} + +// UpdateRefundRequestedAt sets the "refund_requested_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundRequestedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundRequestedAt() + }) +} + +// ClearRefundRequestedAt clears the value of the "refund_requested_at" field. +func (u *PaymentOrderUpsertOne) ClearRefundRequestedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundRequestedAt() + }) +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (u *PaymentOrderUpsertOne) SetRefundRequestReason(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundRequestReason(v) + }) +} + +// UpdateRefundRequestReason sets the "refund_request_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundRequestReason() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundRequestReason() + }) +} + +// ClearRefundRequestReason clears the value of the "refund_request_reason" field. +func (u *PaymentOrderUpsertOne) ClearRefundRequestReason() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundRequestReason() + }) +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (u *PaymentOrderUpsertOne) SetRefundRequestedBy(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundRequestedBy(v) + }) +} + +// UpdateRefundRequestedBy sets the "refund_requested_by" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundRequestedBy() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundRequestedBy() + }) +} + +// ClearRefundRequestedBy clears the value of the "refund_requested_by" field. +func (u *PaymentOrderUpsertOne) ClearRefundRequestedBy() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundRequestedBy() + }) +} + +// SetExpiresAt sets the "expires_at" field. +func (u *PaymentOrderUpsertOne) SetExpiresAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetExpiresAt(v) + }) +} + +// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateExpiresAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateExpiresAt() + }) +} + +// SetPaidAt sets the "paid_at" field. +func (u *PaymentOrderUpsertOne) SetPaidAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPaidAt(v) + }) +} + +// UpdatePaidAt sets the "paid_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdatePaidAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePaidAt() + }) +} + +// ClearPaidAt clears the value of the "paid_at" field. +func (u *PaymentOrderUpsertOne) ClearPaidAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearPaidAt() + }) +} + +// SetCompletedAt sets the "completed_at" field. +func (u *PaymentOrderUpsertOne) SetCompletedAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetCompletedAt(v) + }) +} + +// UpdateCompletedAt sets the "completed_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateCompletedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateCompletedAt() + }) +} + +// ClearCompletedAt clears the value of the "completed_at" field. +func (u *PaymentOrderUpsertOne) ClearCompletedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearCompletedAt() + }) +} + +// SetFailedAt sets the "failed_at" field. +func (u *PaymentOrderUpsertOne) SetFailedAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetFailedAt(v) + }) +} + +// UpdateFailedAt sets the "failed_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateFailedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateFailedAt() + }) +} + +// ClearFailedAt clears the value of the "failed_at" field. +func (u *PaymentOrderUpsertOne) ClearFailedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearFailedAt() + }) +} + +// SetFailedReason sets the "failed_reason" field. +func (u *PaymentOrderUpsertOne) SetFailedReason(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetFailedReason(v) + }) +} + +// UpdateFailedReason sets the "failed_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateFailedReason() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateFailedReason() + }) +} + +// ClearFailedReason clears the value of the "failed_reason" field. +func (u *PaymentOrderUpsertOne) ClearFailedReason() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearFailedReason() + }) +} + +// SetClientIP sets the "client_ip" field. +func (u *PaymentOrderUpsertOne) SetClientIP(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetClientIP(v) + }) +} + +// UpdateClientIP sets the "client_ip" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateClientIP() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateClientIP() + }) +} + +// SetSrcHost sets the "src_host" field. +func (u *PaymentOrderUpsertOne) SetSrcHost(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSrcHost(v) + }) +} + +// UpdateSrcHost sets the "src_host" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateSrcHost() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSrcHost() + }) +} + +// SetSrcURL sets the "src_url" field. +func (u *PaymentOrderUpsertOne) SetSrcURL(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSrcURL(v) + }) +} + +// UpdateSrcURL sets the "src_url" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateSrcURL() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSrcURL() + }) +} + +// ClearSrcURL clears the value of the "src_url" field. +func (u *PaymentOrderUpsertOne) ClearSrcURL() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearSrcURL() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *PaymentOrderUpsertOne) SetUpdatedAt(v time.Time) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateUpdatedAt() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *PaymentOrderUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for PaymentOrderCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *PaymentOrderUpsertOne) 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 *PaymentOrderUpsertOne) 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 *PaymentOrderUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// PaymentOrderCreateBulk is the builder for creating many PaymentOrder entities in bulk. +type PaymentOrderCreateBulk struct { + config + err error + builders []*PaymentOrderCreate + conflict []sql.ConflictOption +} + +// Save creates the PaymentOrder entities in the database. +func (_c *PaymentOrderCreateBulk) Save(ctx context.Context) ([]*PaymentOrder, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*PaymentOrder, 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.(*PaymentOrderMutation) + 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 *PaymentOrderCreateBulk) SaveX(ctx context.Context) []*PaymentOrder { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *PaymentOrderCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *PaymentOrderCreateBulk) 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.PaymentOrder.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.PaymentOrderUpsert) { +// SetUserID(v+v). +// }). +// Exec(ctx) +func (_c *PaymentOrderCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentOrderUpsertBulk { + _c.conflict = opts + return &PaymentOrderUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.PaymentOrder.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *PaymentOrderCreateBulk) OnConflictColumns(columns ...string) *PaymentOrderUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &PaymentOrderUpsertBulk{ + create: _c, + } +} + +// PaymentOrderUpsertBulk is the builder for "upsert"-ing +// a bulk of PaymentOrder nodes. +type PaymentOrderUpsertBulk struct { + create *PaymentOrderCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.PaymentOrder.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *PaymentOrderUpsertBulk) UpdateNewValues() *PaymentOrderUpsertBulk { + 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(paymentorder.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.PaymentOrder.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *PaymentOrderUpsertBulk) Ignore() *PaymentOrderUpsertBulk { + 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 *PaymentOrderUpsertBulk) DoNothing() *PaymentOrderUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the PaymentOrderCreateBulk.OnConflict +// documentation for more info. +func (u *PaymentOrderUpsertBulk) Update(set func(*PaymentOrderUpsert)) *PaymentOrderUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&PaymentOrderUpsert{UpdateSet: update}) + })) + return u +} + +// SetUserID sets the "user_id" field. +func (u *PaymentOrderUpsertBulk) SetUserID(v int64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserID(v) + }) +} + +// UpdateUserID sets the "user_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateUserID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserID() + }) +} + +// SetUserEmail sets the "user_email" field. +func (u *PaymentOrderUpsertBulk) SetUserEmail(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserEmail(v) + }) +} + +// UpdateUserEmail sets the "user_email" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateUserEmail() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserEmail() + }) +} + +// SetUserName sets the "user_name" field. +func (u *PaymentOrderUpsertBulk) SetUserName(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserName(v) + }) +} + +// UpdateUserName sets the "user_name" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateUserName() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserName() + }) +} + +// SetUserNotes sets the "user_notes" field. +func (u *PaymentOrderUpsertBulk) SetUserNotes(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUserNotes(v) + }) +} + +// UpdateUserNotes sets the "user_notes" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateUserNotes() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUserNotes() + }) +} + +// ClearUserNotes clears the value of the "user_notes" field. +func (u *PaymentOrderUpsertBulk) ClearUserNotes() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearUserNotes() + }) +} + +// SetAmount sets the "amount" field. +func (u *PaymentOrderUpsertBulk) SetAmount(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetAmount(v) + }) +} + +// AddAmount adds v to the "amount" field. +func (u *PaymentOrderUpsertBulk) AddAmount(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddAmount(v) + }) +} + +// UpdateAmount sets the "amount" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateAmount() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateAmount() + }) +} + +// SetPayAmount sets the "pay_amount" field. +func (u *PaymentOrderUpsertBulk) SetPayAmount(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPayAmount(v) + }) +} + +// AddPayAmount adds v to the "pay_amount" field. +func (u *PaymentOrderUpsertBulk) AddPayAmount(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddPayAmount(v) + }) +} + +// UpdatePayAmount sets the "pay_amount" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdatePayAmount() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePayAmount() + }) +} + +// SetFeeRate sets the "fee_rate" field. +func (u *PaymentOrderUpsertBulk) SetFeeRate(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetFeeRate(v) + }) +} + +// AddFeeRate adds v to the "fee_rate" field. +func (u *PaymentOrderUpsertBulk) AddFeeRate(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddFeeRate(v) + }) +} + +// UpdateFeeRate sets the "fee_rate" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateFeeRate() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateFeeRate() + }) +} + +// SetRechargeCode sets the "recharge_code" field. +func (u *PaymentOrderUpsertBulk) SetRechargeCode(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRechargeCode(v) + }) +} + +// UpdateRechargeCode sets the "recharge_code" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRechargeCode() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRechargeCode() + }) +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (u *PaymentOrderUpsertBulk) SetOutTradeNo(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetOutTradeNo(v) + }) +} + +// UpdateOutTradeNo sets the "out_trade_no" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateOutTradeNo() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateOutTradeNo() + }) +} + +// SetPaymentType sets the "payment_type" field. +func (u *PaymentOrderUpsertBulk) SetPaymentType(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPaymentType(v) + }) +} + +// UpdatePaymentType sets the "payment_type" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdatePaymentType() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePaymentType() + }) +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (u *PaymentOrderUpsertBulk) SetPaymentTradeNo(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPaymentTradeNo(v) + }) +} + +// UpdatePaymentTradeNo sets the "payment_trade_no" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdatePaymentTradeNo() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePaymentTradeNo() + }) +} + +// SetPayURL sets the "pay_url" field. +func (u *PaymentOrderUpsertBulk) SetPayURL(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPayURL(v) + }) +} + +// UpdatePayURL sets the "pay_url" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdatePayURL() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePayURL() + }) +} + +// ClearPayURL clears the value of the "pay_url" field. +func (u *PaymentOrderUpsertBulk) ClearPayURL() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearPayURL() + }) +} + +// SetQrCode sets the "qr_code" field. +func (u *PaymentOrderUpsertBulk) SetQrCode(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetQrCode(v) + }) +} + +// UpdateQrCode sets the "qr_code" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateQrCode() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateQrCode() + }) +} + +// ClearQrCode clears the value of the "qr_code" field. +func (u *PaymentOrderUpsertBulk) ClearQrCode() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearQrCode() + }) +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (u *PaymentOrderUpsertBulk) SetQrCodeImg(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetQrCodeImg(v) + }) +} + +// UpdateQrCodeImg sets the "qr_code_img" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateQrCodeImg() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateQrCodeImg() + }) +} + +// ClearQrCodeImg clears the value of the "qr_code_img" field. +func (u *PaymentOrderUpsertBulk) ClearQrCodeImg() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearQrCodeImg() + }) +} + +// SetOrderType sets the "order_type" field. +func (u *PaymentOrderUpsertBulk) SetOrderType(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetOrderType(v) + }) +} + +// UpdateOrderType sets the "order_type" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateOrderType() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateOrderType() + }) +} + +// SetPlanID sets the "plan_id" field. +func (u *PaymentOrderUpsertBulk) SetPlanID(v int64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPlanID(v) + }) +} + +// AddPlanID adds v to the "plan_id" field. +func (u *PaymentOrderUpsertBulk) AddPlanID(v int64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddPlanID(v) + }) +} + +// UpdatePlanID sets the "plan_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdatePlanID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePlanID() + }) +} + +// ClearPlanID clears the value of the "plan_id" field. +func (u *PaymentOrderUpsertBulk) ClearPlanID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearPlanID() + }) +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (u *PaymentOrderUpsertBulk) SetSubscriptionGroupID(v int64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSubscriptionGroupID(v) + }) +} + +// AddSubscriptionGroupID adds v to the "subscription_group_id" field. +func (u *PaymentOrderUpsertBulk) AddSubscriptionGroupID(v int64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddSubscriptionGroupID(v) + }) +} + +// UpdateSubscriptionGroupID sets the "subscription_group_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateSubscriptionGroupID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSubscriptionGroupID() + }) +} + +// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field. +func (u *PaymentOrderUpsertBulk) ClearSubscriptionGroupID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearSubscriptionGroupID() + }) +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (u *PaymentOrderUpsertBulk) SetSubscriptionDays(v int) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSubscriptionDays(v) + }) +} + +// AddSubscriptionDays adds v to the "subscription_days" field. +func (u *PaymentOrderUpsertBulk) AddSubscriptionDays(v int) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddSubscriptionDays(v) + }) +} + +// UpdateSubscriptionDays sets the "subscription_days" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateSubscriptionDays() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSubscriptionDays() + }) +} + +// ClearSubscriptionDays clears the value of the "subscription_days" field. +func (u *PaymentOrderUpsertBulk) ClearSubscriptionDays() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearSubscriptionDays() + }) +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (u *PaymentOrderUpsertBulk) SetProviderInstanceID(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetProviderInstanceID(v) + }) +} + +// UpdateProviderInstanceID sets the "provider_instance_id" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateProviderInstanceID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateProviderInstanceID() + }) +} + +// ClearProviderInstanceID clears the value of the "provider_instance_id" field. +func (u *PaymentOrderUpsertBulk) ClearProviderInstanceID() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearProviderInstanceID() + }) +} + +// SetStatus sets the "status" field. +func (u *PaymentOrderUpsertBulk) SetStatus(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetStatus(v) + }) +} + +// UpdateStatus sets the "status" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateStatus() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateStatus() + }) +} + +// SetRefundAmount sets the "refund_amount" field. +func (u *PaymentOrderUpsertBulk) SetRefundAmount(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundAmount(v) + }) +} + +// AddRefundAmount adds v to the "refund_amount" field. +func (u *PaymentOrderUpsertBulk) AddRefundAmount(v float64) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.AddRefundAmount(v) + }) +} + +// UpdateRefundAmount sets the "refund_amount" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundAmount() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundAmount() + }) +} + +// SetRefundReason sets the "refund_reason" field. +func (u *PaymentOrderUpsertBulk) SetRefundReason(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundReason(v) + }) +} + +// UpdateRefundReason sets the "refund_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundReason() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundReason() + }) +} + +// ClearRefundReason clears the value of the "refund_reason" field. +func (u *PaymentOrderUpsertBulk) ClearRefundReason() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundReason() + }) +} + +// SetRefundAt sets the "refund_at" field. +func (u *PaymentOrderUpsertBulk) SetRefundAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundAt(v) + }) +} + +// UpdateRefundAt sets the "refund_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundAt() + }) +} + +// ClearRefundAt clears the value of the "refund_at" field. +func (u *PaymentOrderUpsertBulk) ClearRefundAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundAt() + }) +} + +// SetForceRefund sets the "force_refund" field. +func (u *PaymentOrderUpsertBulk) SetForceRefund(v bool) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetForceRefund(v) + }) +} + +// UpdateForceRefund sets the "force_refund" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateForceRefund() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateForceRefund() + }) +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (u *PaymentOrderUpsertBulk) SetRefundRequestedAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundRequestedAt(v) + }) +} + +// UpdateRefundRequestedAt sets the "refund_requested_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundRequestedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundRequestedAt() + }) +} + +// ClearRefundRequestedAt clears the value of the "refund_requested_at" field. +func (u *PaymentOrderUpsertBulk) ClearRefundRequestedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundRequestedAt() + }) +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (u *PaymentOrderUpsertBulk) SetRefundRequestReason(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundRequestReason(v) + }) +} + +// UpdateRefundRequestReason sets the "refund_request_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundRequestReason() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundRequestReason() + }) +} + +// ClearRefundRequestReason clears the value of the "refund_request_reason" field. +func (u *PaymentOrderUpsertBulk) ClearRefundRequestReason() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundRequestReason() + }) +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (u *PaymentOrderUpsertBulk) SetRefundRequestedBy(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetRefundRequestedBy(v) + }) +} + +// UpdateRefundRequestedBy sets the "refund_requested_by" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundRequestedBy() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateRefundRequestedBy() + }) +} + +// ClearRefundRequestedBy clears the value of the "refund_requested_by" field. +func (u *PaymentOrderUpsertBulk) ClearRefundRequestedBy() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearRefundRequestedBy() + }) +} + +// SetExpiresAt sets the "expires_at" field. +func (u *PaymentOrderUpsertBulk) SetExpiresAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetExpiresAt(v) + }) +} + +// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateExpiresAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateExpiresAt() + }) +} + +// SetPaidAt sets the "paid_at" field. +func (u *PaymentOrderUpsertBulk) SetPaidAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetPaidAt(v) + }) +} + +// UpdatePaidAt sets the "paid_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdatePaidAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdatePaidAt() + }) +} + +// ClearPaidAt clears the value of the "paid_at" field. +func (u *PaymentOrderUpsertBulk) ClearPaidAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearPaidAt() + }) +} + +// SetCompletedAt sets the "completed_at" field. +func (u *PaymentOrderUpsertBulk) SetCompletedAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetCompletedAt(v) + }) +} + +// UpdateCompletedAt sets the "completed_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateCompletedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateCompletedAt() + }) +} + +// ClearCompletedAt clears the value of the "completed_at" field. +func (u *PaymentOrderUpsertBulk) ClearCompletedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearCompletedAt() + }) +} + +// SetFailedAt sets the "failed_at" field. +func (u *PaymentOrderUpsertBulk) SetFailedAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetFailedAt(v) + }) +} + +// UpdateFailedAt sets the "failed_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateFailedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateFailedAt() + }) +} + +// ClearFailedAt clears the value of the "failed_at" field. +func (u *PaymentOrderUpsertBulk) ClearFailedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearFailedAt() + }) +} + +// SetFailedReason sets the "failed_reason" field. +func (u *PaymentOrderUpsertBulk) SetFailedReason(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetFailedReason(v) + }) +} + +// UpdateFailedReason sets the "failed_reason" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateFailedReason() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateFailedReason() + }) +} + +// ClearFailedReason clears the value of the "failed_reason" field. +func (u *PaymentOrderUpsertBulk) ClearFailedReason() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearFailedReason() + }) +} + +// SetClientIP sets the "client_ip" field. +func (u *PaymentOrderUpsertBulk) SetClientIP(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetClientIP(v) + }) +} + +// UpdateClientIP sets the "client_ip" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateClientIP() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateClientIP() + }) +} + +// SetSrcHost sets the "src_host" field. +func (u *PaymentOrderUpsertBulk) SetSrcHost(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSrcHost(v) + }) +} + +// UpdateSrcHost sets the "src_host" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateSrcHost() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSrcHost() + }) +} + +// SetSrcURL sets the "src_url" field. +func (u *PaymentOrderUpsertBulk) SetSrcURL(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetSrcURL(v) + }) +} + +// UpdateSrcURL sets the "src_url" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateSrcURL() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateSrcURL() + }) +} + +// ClearSrcURL clears the value of the "src_url" field. +func (u *PaymentOrderUpsertBulk) ClearSrcURL() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearSrcURL() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *PaymentOrderUpsertBulk) SetUpdatedAt(v time.Time) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateUpdatedAt() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *PaymentOrderUpsertBulk) 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 PaymentOrderCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for PaymentOrderCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *PaymentOrderUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/paymentorder_delete.go b/backend/ent/paymentorder_delete.go new file mode 100644 index 0000000000000000000000000000000000000000..a4bc1bdf6d124a595c38d295acf28930de3933b5 --- /dev/null +++ b/backend/ent/paymentorder_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/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentOrderDelete is the builder for deleting a PaymentOrder entity. +type PaymentOrderDelete struct { + config + hooks []Hook + mutation *PaymentOrderMutation +} + +// Where appends a list predicates to the PaymentOrderDelete builder. +func (_d *PaymentOrderDelete) Where(ps ...predicate.PaymentOrder) *PaymentOrderDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *PaymentOrderDelete) 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 *PaymentOrderDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *PaymentOrderDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(paymentorder.Table, sqlgraph.NewFieldSpec(paymentorder.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 +} + +// PaymentOrderDeleteOne is the builder for deleting a single PaymentOrder entity. +type PaymentOrderDeleteOne struct { + _d *PaymentOrderDelete +} + +// Where appends a list predicates to the PaymentOrderDelete builder. +func (_d *PaymentOrderDeleteOne) Where(ps ...predicate.PaymentOrder) *PaymentOrderDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *PaymentOrderDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{paymentorder.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *PaymentOrderDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/paymentorder_query.go b/backend/ent/paymentorder_query.go new file mode 100644 index 0000000000000000000000000000000000000000..92fd74a73c48a97178c1f98e2b49b6566976b39d --- /dev/null +++ b/backend/ent/paymentorder_query.go @@ -0,0 +1,643 @@ +// 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/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/user" +) + +// PaymentOrderQuery is the builder for querying PaymentOrder entities. +type PaymentOrderQuery struct { + config + ctx *QueryContext + order []paymentorder.OrderOption + inters []Interceptor + predicates []predicate.PaymentOrder + 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 PaymentOrderQuery builder. +func (_q *PaymentOrderQuery) Where(ps ...predicate.PaymentOrder) *PaymentOrderQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *PaymentOrderQuery) Limit(limit int) *PaymentOrderQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *PaymentOrderQuery) Offset(offset int) *PaymentOrderQuery { + _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 *PaymentOrderQuery) Unique(unique bool) *PaymentOrderQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *PaymentOrderQuery) Order(o ...paymentorder.OrderOption) *PaymentOrderQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QueryUser chains the current query on the "user" edge. +func (_q *PaymentOrderQuery) 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(paymentorder.Table, paymentorder.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, paymentorder.UserTable, paymentorder.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first PaymentOrder entity from the query. +// Returns a *NotFoundError when no PaymentOrder was found. +func (_q *PaymentOrderQuery) First(ctx context.Context) (*PaymentOrder, 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{paymentorder.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *PaymentOrderQuery) FirstX(ctx context.Context) *PaymentOrder { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first PaymentOrder ID from the query. +// Returns a *NotFoundError when no PaymentOrder ID was found. +func (_q *PaymentOrderQuery) 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{paymentorder.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *PaymentOrderQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single PaymentOrder entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one PaymentOrder entity is found. +// Returns a *NotFoundError when no PaymentOrder entities are found. +func (_q *PaymentOrderQuery) Only(ctx context.Context) (*PaymentOrder, 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{paymentorder.Label} + default: + return nil, &NotSingularError{paymentorder.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *PaymentOrderQuery) OnlyX(ctx context.Context) *PaymentOrder { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only PaymentOrder ID in the query. +// Returns a *NotSingularError when more than one PaymentOrder ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *PaymentOrderQuery) 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{paymentorder.Label} + default: + err = &NotSingularError{paymentorder.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *PaymentOrderQuery) 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 PaymentOrders. +func (_q *PaymentOrderQuery) All(ctx context.Context) ([]*PaymentOrder, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*PaymentOrder, *PaymentOrderQuery]() + return withInterceptors[[]*PaymentOrder](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *PaymentOrderQuery) AllX(ctx context.Context) []*PaymentOrder { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of PaymentOrder IDs. +func (_q *PaymentOrderQuery) 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(paymentorder.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *PaymentOrderQuery) 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 *PaymentOrderQuery) 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[*PaymentOrderQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *PaymentOrderQuery) 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 *PaymentOrderQuery) 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 *PaymentOrderQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the PaymentOrderQuery 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 *PaymentOrderQuery) Clone() *PaymentOrderQuery { + if _q == nil { + return nil + } + return &PaymentOrderQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]paymentorder.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.PaymentOrder{}, _q.predicates...), + withUser: _q.withUser.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// 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 *PaymentOrderQuery) WithUser(opts ...func(*UserQuery)) *PaymentOrderQuery { + 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 { +// UserID int64 `json:"user_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.PaymentOrder.Query(). +// GroupBy(paymentorder.FieldUserID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *PaymentOrderQuery) GroupBy(field string, fields ...string) *PaymentOrderGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &PaymentOrderGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = paymentorder.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 { +// UserID int64 `json:"user_id,omitempty"` +// } +// +// client.PaymentOrder.Query(). +// Select(paymentorder.FieldUserID). +// Scan(ctx, &v) +func (_q *PaymentOrderQuery) Select(fields ...string) *PaymentOrderSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &PaymentOrderSelect{PaymentOrderQuery: _q} + sbuild.label = paymentorder.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a PaymentOrderSelect configured with the given aggregations. +func (_q *PaymentOrderQuery) Aggregate(fns ...AggregateFunc) *PaymentOrderSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *PaymentOrderQuery) 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 !paymentorder.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 *PaymentOrderQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentOrder, error) { + var ( + nodes = []*PaymentOrder{} + _spec = _q.querySpec() + loadedTypes = [1]bool{ + _q.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*PaymentOrder).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &PaymentOrder{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.withUser; query != nil { + if err := _q.loadUser(ctx, query, nodes, nil, + func(n *PaymentOrder, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *PaymentOrderQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*PaymentOrder, init func(*PaymentOrder), assign func(*PaymentOrder, *User)) error { + ids := make([]int64, 0, len(nodes)) + nodeids := make(map[int64][]*PaymentOrder) + 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 *PaymentOrderQuery) 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 *PaymentOrderQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.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, paymentorder.FieldID) + for i := range fields { + if fields[i] != paymentorder.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if _q.withUser != nil { + _spec.Node.AddColumnOnce(paymentorder.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 *PaymentOrderQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(paymentorder.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = paymentorder.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 *PaymentOrderQuery) ForUpdate(opts ...sql.LockOption) *PaymentOrderQuery { + 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 *PaymentOrderQuery) ForShare(opts ...sql.LockOption) *PaymentOrderQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// PaymentOrderGroupBy is the group-by builder for PaymentOrder entities. +type PaymentOrderGroupBy struct { + selector + build *PaymentOrderQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *PaymentOrderGroupBy) Aggregate(fns ...AggregateFunc) *PaymentOrderGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *PaymentOrderGroupBy) 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[*PaymentOrderQuery, *PaymentOrderGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *PaymentOrderGroupBy) sqlScan(ctx context.Context, root *PaymentOrderQuery, 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) +} + +// PaymentOrderSelect is the builder for selecting fields of PaymentOrder entities. +type PaymentOrderSelect struct { + *PaymentOrderQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *PaymentOrderSelect) Aggregate(fns ...AggregateFunc) *PaymentOrderSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *PaymentOrderSelect) 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[*PaymentOrderQuery, *PaymentOrderSelect](ctx, _s.PaymentOrderQuery, _s, _s.inters, v) +} + +func (_s *PaymentOrderSelect) sqlScan(ctx context.Context, root *PaymentOrderQuery, 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/paymentorder_update.go b/backend/ent/paymentorder_update.go new file mode 100644 index 0000000000000000000000000000000000000000..5978fc29148618f828e6586f8baf59d24c64ed1f --- /dev/null +++ b/backend/ent/paymentorder_update.go @@ -0,0 +1,2083 @@ +// 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/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/user" +) + +// PaymentOrderUpdate is the builder for updating PaymentOrder entities. +type PaymentOrderUpdate struct { + config + hooks []Hook + mutation *PaymentOrderMutation +} + +// Where appends a list predicates to the PaymentOrderUpdate builder. +func (_u *PaymentOrderUpdate) Where(ps ...predicate.PaymentOrder) *PaymentOrderUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetUserID sets the "user_id" field. +func (_u *PaymentOrderUpdate) SetUserID(v int64) *PaymentOrderUpdate { + _u.mutation.SetUserID(v) + return _u +} + +// SetNillableUserID sets the "user_id" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableUserID(v *int64) *PaymentOrderUpdate { + if v != nil { + _u.SetUserID(*v) + } + return _u +} + +// SetUserEmail sets the "user_email" field. +func (_u *PaymentOrderUpdate) SetUserEmail(v string) *PaymentOrderUpdate { + _u.mutation.SetUserEmail(v) + return _u +} + +// SetNillableUserEmail sets the "user_email" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableUserEmail(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetUserEmail(*v) + } + return _u +} + +// SetUserName sets the "user_name" field. +func (_u *PaymentOrderUpdate) SetUserName(v string) *PaymentOrderUpdate { + _u.mutation.SetUserName(v) + return _u +} + +// SetNillableUserName sets the "user_name" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableUserName(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetUserName(*v) + } + return _u +} + +// SetUserNotes sets the "user_notes" field. +func (_u *PaymentOrderUpdate) SetUserNotes(v string) *PaymentOrderUpdate { + _u.mutation.SetUserNotes(v) + return _u +} + +// SetNillableUserNotes sets the "user_notes" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableUserNotes(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetUserNotes(*v) + } + return _u +} + +// ClearUserNotes clears the value of the "user_notes" field. +func (_u *PaymentOrderUpdate) ClearUserNotes() *PaymentOrderUpdate { + _u.mutation.ClearUserNotes() + return _u +} + +// SetAmount sets the "amount" field. +func (_u *PaymentOrderUpdate) SetAmount(v float64) *PaymentOrderUpdate { + _u.mutation.ResetAmount() + _u.mutation.SetAmount(v) + return _u +} + +// SetNillableAmount sets the "amount" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableAmount(v *float64) *PaymentOrderUpdate { + if v != nil { + _u.SetAmount(*v) + } + return _u +} + +// AddAmount adds value to the "amount" field. +func (_u *PaymentOrderUpdate) AddAmount(v float64) *PaymentOrderUpdate { + _u.mutation.AddAmount(v) + return _u +} + +// SetPayAmount sets the "pay_amount" field. +func (_u *PaymentOrderUpdate) SetPayAmount(v float64) *PaymentOrderUpdate { + _u.mutation.ResetPayAmount() + _u.mutation.SetPayAmount(v) + return _u +} + +// SetNillablePayAmount sets the "pay_amount" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillablePayAmount(v *float64) *PaymentOrderUpdate { + if v != nil { + _u.SetPayAmount(*v) + } + return _u +} + +// AddPayAmount adds value to the "pay_amount" field. +func (_u *PaymentOrderUpdate) AddPayAmount(v float64) *PaymentOrderUpdate { + _u.mutation.AddPayAmount(v) + return _u +} + +// SetFeeRate sets the "fee_rate" field. +func (_u *PaymentOrderUpdate) SetFeeRate(v float64) *PaymentOrderUpdate { + _u.mutation.ResetFeeRate() + _u.mutation.SetFeeRate(v) + return _u +} + +// SetNillableFeeRate sets the "fee_rate" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableFeeRate(v *float64) *PaymentOrderUpdate { + if v != nil { + _u.SetFeeRate(*v) + } + return _u +} + +// AddFeeRate adds value to the "fee_rate" field. +func (_u *PaymentOrderUpdate) AddFeeRate(v float64) *PaymentOrderUpdate { + _u.mutation.AddFeeRate(v) + return _u +} + +// SetRechargeCode sets the "recharge_code" field. +func (_u *PaymentOrderUpdate) SetRechargeCode(v string) *PaymentOrderUpdate { + _u.mutation.SetRechargeCode(v) + return _u +} + +// SetNillableRechargeCode sets the "recharge_code" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRechargeCode(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetRechargeCode(*v) + } + return _u +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (_u *PaymentOrderUpdate) SetOutTradeNo(v string) *PaymentOrderUpdate { + _u.mutation.SetOutTradeNo(v) + return _u +} + +// SetNillableOutTradeNo sets the "out_trade_no" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableOutTradeNo(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetOutTradeNo(*v) + } + return _u +} + +// SetPaymentType sets the "payment_type" field. +func (_u *PaymentOrderUpdate) SetPaymentType(v string) *PaymentOrderUpdate { + _u.mutation.SetPaymentType(v) + return _u +} + +// SetNillablePaymentType sets the "payment_type" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillablePaymentType(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetPaymentType(*v) + } + return _u +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (_u *PaymentOrderUpdate) SetPaymentTradeNo(v string) *PaymentOrderUpdate { + _u.mutation.SetPaymentTradeNo(v) + return _u +} + +// SetNillablePaymentTradeNo sets the "payment_trade_no" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillablePaymentTradeNo(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetPaymentTradeNo(*v) + } + return _u +} + +// SetPayURL sets the "pay_url" field. +func (_u *PaymentOrderUpdate) SetPayURL(v string) *PaymentOrderUpdate { + _u.mutation.SetPayURL(v) + return _u +} + +// SetNillablePayURL sets the "pay_url" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillablePayURL(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetPayURL(*v) + } + return _u +} + +// ClearPayURL clears the value of the "pay_url" field. +func (_u *PaymentOrderUpdate) ClearPayURL() *PaymentOrderUpdate { + _u.mutation.ClearPayURL() + return _u +} + +// SetQrCode sets the "qr_code" field. +func (_u *PaymentOrderUpdate) SetQrCode(v string) *PaymentOrderUpdate { + _u.mutation.SetQrCode(v) + return _u +} + +// SetNillableQrCode sets the "qr_code" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableQrCode(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetQrCode(*v) + } + return _u +} + +// ClearQrCode clears the value of the "qr_code" field. +func (_u *PaymentOrderUpdate) ClearQrCode() *PaymentOrderUpdate { + _u.mutation.ClearQrCode() + return _u +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (_u *PaymentOrderUpdate) SetQrCodeImg(v string) *PaymentOrderUpdate { + _u.mutation.SetQrCodeImg(v) + return _u +} + +// SetNillableQrCodeImg sets the "qr_code_img" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableQrCodeImg(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetQrCodeImg(*v) + } + return _u +} + +// ClearQrCodeImg clears the value of the "qr_code_img" field. +func (_u *PaymentOrderUpdate) ClearQrCodeImg() *PaymentOrderUpdate { + _u.mutation.ClearQrCodeImg() + return _u +} + +// SetOrderType sets the "order_type" field. +func (_u *PaymentOrderUpdate) SetOrderType(v string) *PaymentOrderUpdate { + _u.mutation.SetOrderType(v) + return _u +} + +// SetNillableOrderType sets the "order_type" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableOrderType(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetOrderType(*v) + } + return _u +} + +// SetPlanID sets the "plan_id" field. +func (_u *PaymentOrderUpdate) SetPlanID(v int64) *PaymentOrderUpdate { + _u.mutation.ResetPlanID() + _u.mutation.SetPlanID(v) + return _u +} + +// SetNillablePlanID sets the "plan_id" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillablePlanID(v *int64) *PaymentOrderUpdate { + if v != nil { + _u.SetPlanID(*v) + } + return _u +} + +// AddPlanID adds value to the "plan_id" field. +func (_u *PaymentOrderUpdate) AddPlanID(v int64) *PaymentOrderUpdate { + _u.mutation.AddPlanID(v) + return _u +} + +// ClearPlanID clears the value of the "plan_id" field. +func (_u *PaymentOrderUpdate) ClearPlanID() *PaymentOrderUpdate { + _u.mutation.ClearPlanID() + return _u +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (_u *PaymentOrderUpdate) SetSubscriptionGroupID(v int64) *PaymentOrderUpdate { + _u.mutation.ResetSubscriptionGroupID() + _u.mutation.SetSubscriptionGroupID(v) + return _u +} + +// SetNillableSubscriptionGroupID sets the "subscription_group_id" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableSubscriptionGroupID(v *int64) *PaymentOrderUpdate { + if v != nil { + _u.SetSubscriptionGroupID(*v) + } + return _u +} + +// AddSubscriptionGroupID adds value to the "subscription_group_id" field. +func (_u *PaymentOrderUpdate) AddSubscriptionGroupID(v int64) *PaymentOrderUpdate { + _u.mutation.AddSubscriptionGroupID(v) + return _u +} + +// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field. +func (_u *PaymentOrderUpdate) ClearSubscriptionGroupID() *PaymentOrderUpdate { + _u.mutation.ClearSubscriptionGroupID() + return _u +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (_u *PaymentOrderUpdate) SetSubscriptionDays(v int) *PaymentOrderUpdate { + _u.mutation.ResetSubscriptionDays() + _u.mutation.SetSubscriptionDays(v) + return _u +} + +// SetNillableSubscriptionDays sets the "subscription_days" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableSubscriptionDays(v *int) *PaymentOrderUpdate { + if v != nil { + _u.SetSubscriptionDays(*v) + } + return _u +} + +// AddSubscriptionDays adds value to the "subscription_days" field. +func (_u *PaymentOrderUpdate) AddSubscriptionDays(v int) *PaymentOrderUpdate { + _u.mutation.AddSubscriptionDays(v) + return _u +} + +// ClearSubscriptionDays clears the value of the "subscription_days" field. +func (_u *PaymentOrderUpdate) ClearSubscriptionDays() *PaymentOrderUpdate { + _u.mutation.ClearSubscriptionDays() + return _u +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (_u *PaymentOrderUpdate) SetProviderInstanceID(v string) *PaymentOrderUpdate { + _u.mutation.SetProviderInstanceID(v) + return _u +} + +// SetNillableProviderInstanceID sets the "provider_instance_id" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableProviderInstanceID(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetProviderInstanceID(*v) + } + return _u +} + +// ClearProviderInstanceID clears the value of the "provider_instance_id" field. +func (_u *PaymentOrderUpdate) ClearProviderInstanceID() *PaymentOrderUpdate { + _u.mutation.ClearProviderInstanceID() + return _u +} + +// SetStatus sets the "status" field. +func (_u *PaymentOrderUpdate) SetStatus(v string) *PaymentOrderUpdate { + _u.mutation.SetStatus(v) + return _u +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableStatus(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetStatus(*v) + } + return _u +} + +// SetRefundAmount sets the "refund_amount" field. +func (_u *PaymentOrderUpdate) SetRefundAmount(v float64) *PaymentOrderUpdate { + _u.mutation.ResetRefundAmount() + _u.mutation.SetRefundAmount(v) + return _u +} + +// SetNillableRefundAmount sets the "refund_amount" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundAmount(v *float64) *PaymentOrderUpdate { + if v != nil { + _u.SetRefundAmount(*v) + } + return _u +} + +// AddRefundAmount adds value to the "refund_amount" field. +func (_u *PaymentOrderUpdate) AddRefundAmount(v float64) *PaymentOrderUpdate { + _u.mutation.AddRefundAmount(v) + return _u +} + +// SetRefundReason sets the "refund_reason" field. +func (_u *PaymentOrderUpdate) SetRefundReason(v string) *PaymentOrderUpdate { + _u.mutation.SetRefundReason(v) + return _u +} + +// SetNillableRefundReason sets the "refund_reason" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundReason(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetRefundReason(*v) + } + return _u +} + +// ClearRefundReason clears the value of the "refund_reason" field. +func (_u *PaymentOrderUpdate) ClearRefundReason() *PaymentOrderUpdate { + _u.mutation.ClearRefundReason() + return _u +} + +// SetRefundAt sets the "refund_at" field. +func (_u *PaymentOrderUpdate) SetRefundAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetRefundAt(v) + return _u +} + +// SetNillableRefundAt sets the "refund_at" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundAt(v *time.Time) *PaymentOrderUpdate { + if v != nil { + _u.SetRefundAt(*v) + } + return _u +} + +// ClearRefundAt clears the value of the "refund_at" field. +func (_u *PaymentOrderUpdate) ClearRefundAt() *PaymentOrderUpdate { + _u.mutation.ClearRefundAt() + return _u +} + +// SetForceRefund sets the "force_refund" field. +func (_u *PaymentOrderUpdate) SetForceRefund(v bool) *PaymentOrderUpdate { + _u.mutation.SetForceRefund(v) + return _u +} + +// SetNillableForceRefund sets the "force_refund" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableForceRefund(v *bool) *PaymentOrderUpdate { + if v != nil { + _u.SetForceRefund(*v) + } + return _u +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (_u *PaymentOrderUpdate) SetRefundRequestedAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetRefundRequestedAt(v) + return _u +} + +// SetNillableRefundRequestedAt sets the "refund_requested_at" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundRequestedAt(v *time.Time) *PaymentOrderUpdate { + if v != nil { + _u.SetRefundRequestedAt(*v) + } + return _u +} + +// ClearRefundRequestedAt clears the value of the "refund_requested_at" field. +func (_u *PaymentOrderUpdate) ClearRefundRequestedAt() *PaymentOrderUpdate { + _u.mutation.ClearRefundRequestedAt() + return _u +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (_u *PaymentOrderUpdate) SetRefundRequestReason(v string) *PaymentOrderUpdate { + _u.mutation.SetRefundRequestReason(v) + return _u +} + +// SetNillableRefundRequestReason sets the "refund_request_reason" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundRequestReason(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetRefundRequestReason(*v) + } + return _u +} + +// ClearRefundRequestReason clears the value of the "refund_request_reason" field. +func (_u *PaymentOrderUpdate) ClearRefundRequestReason() *PaymentOrderUpdate { + _u.mutation.ClearRefundRequestReason() + return _u +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (_u *PaymentOrderUpdate) SetRefundRequestedBy(v string) *PaymentOrderUpdate { + _u.mutation.SetRefundRequestedBy(v) + return _u +} + +// SetNillableRefundRequestedBy sets the "refund_requested_by" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundRequestedBy(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetRefundRequestedBy(*v) + } + return _u +} + +// ClearRefundRequestedBy clears the value of the "refund_requested_by" field. +func (_u *PaymentOrderUpdate) ClearRefundRequestedBy() *PaymentOrderUpdate { + _u.mutation.ClearRefundRequestedBy() + return _u +} + +// SetExpiresAt sets the "expires_at" field. +func (_u *PaymentOrderUpdate) SetExpiresAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetExpiresAt(v) + return _u +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableExpiresAt(v *time.Time) *PaymentOrderUpdate { + if v != nil { + _u.SetExpiresAt(*v) + } + return _u +} + +// SetPaidAt sets the "paid_at" field. +func (_u *PaymentOrderUpdate) SetPaidAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetPaidAt(v) + return _u +} + +// SetNillablePaidAt sets the "paid_at" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillablePaidAt(v *time.Time) *PaymentOrderUpdate { + if v != nil { + _u.SetPaidAt(*v) + } + return _u +} + +// ClearPaidAt clears the value of the "paid_at" field. +func (_u *PaymentOrderUpdate) ClearPaidAt() *PaymentOrderUpdate { + _u.mutation.ClearPaidAt() + return _u +} + +// SetCompletedAt sets the "completed_at" field. +func (_u *PaymentOrderUpdate) SetCompletedAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetCompletedAt(v) + return _u +} + +// SetNillableCompletedAt sets the "completed_at" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableCompletedAt(v *time.Time) *PaymentOrderUpdate { + if v != nil { + _u.SetCompletedAt(*v) + } + return _u +} + +// ClearCompletedAt clears the value of the "completed_at" field. +func (_u *PaymentOrderUpdate) ClearCompletedAt() *PaymentOrderUpdate { + _u.mutation.ClearCompletedAt() + return _u +} + +// SetFailedAt sets the "failed_at" field. +func (_u *PaymentOrderUpdate) SetFailedAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetFailedAt(v) + return _u +} + +// SetNillableFailedAt sets the "failed_at" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableFailedAt(v *time.Time) *PaymentOrderUpdate { + if v != nil { + _u.SetFailedAt(*v) + } + return _u +} + +// ClearFailedAt clears the value of the "failed_at" field. +func (_u *PaymentOrderUpdate) ClearFailedAt() *PaymentOrderUpdate { + _u.mutation.ClearFailedAt() + return _u +} + +// SetFailedReason sets the "failed_reason" field. +func (_u *PaymentOrderUpdate) SetFailedReason(v string) *PaymentOrderUpdate { + _u.mutation.SetFailedReason(v) + return _u +} + +// SetNillableFailedReason sets the "failed_reason" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableFailedReason(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetFailedReason(*v) + } + return _u +} + +// ClearFailedReason clears the value of the "failed_reason" field. +func (_u *PaymentOrderUpdate) ClearFailedReason() *PaymentOrderUpdate { + _u.mutation.ClearFailedReason() + return _u +} + +// SetClientIP sets the "client_ip" field. +func (_u *PaymentOrderUpdate) SetClientIP(v string) *PaymentOrderUpdate { + _u.mutation.SetClientIP(v) + return _u +} + +// SetNillableClientIP sets the "client_ip" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableClientIP(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetClientIP(*v) + } + return _u +} + +// SetSrcHost sets the "src_host" field. +func (_u *PaymentOrderUpdate) SetSrcHost(v string) *PaymentOrderUpdate { + _u.mutation.SetSrcHost(v) + return _u +} + +// SetNillableSrcHost sets the "src_host" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableSrcHost(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetSrcHost(*v) + } + return _u +} + +// SetSrcURL sets the "src_url" field. +func (_u *PaymentOrderUpdate) SetSrcURL(v string) *PaymentOrderUpdate { + _u.mutation.SetSrcURL(v) + return _u +} + +// SetNillableSrcURL sets the "src_url" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableSrcURL(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetSrcURL(*v) + } + return _u +} + +// ClearSrcURL clears the value of the "src_url" field. +func (_u *PaymentOrderUpdate) ClearSrcURL() *PaymentOrderUpdate { + _u.mutation.ClearSrcURL() + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *PaymentOrderUpdate) SetUpdatedAt(v time.Time) *PaymentOrderUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetUser sets the "user" edge to the User entity. +func (_u *PaymentOrderUpdate) SetUser(v *User) *PaymentOrderUpdate { + return _u.SetUserID(v.ID) +} + +// Mutation returns the PaymentOrderMutation object of the builder. +func (_u *PaymentOrderUpdate) Mutation() *PaymentOrderMutation { + return _u.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (_u *PaymentOrderUpdate) ClearUser() *PaymentOrderUpdate { + _u.mutation.ClearUser() + return _u +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *PaymentOrderUpdate) 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 *PaymentOrderUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *PaymentOrderUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *PaymentOrderUpdate) 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 *PaymentOrderUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := paymentorder.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *PaymentOrderUpdate) check() error { + if v, ok := _u.mutation.UserEmail(); ok { + if err := paymentorder.UserEmailValidator(v); err != nil { + return &ValidationError{Name: "user_email", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_email": %w`, err)} + } + } + if v, ok := _u.mutation.UserName(); ok { + if err := paymentorder.UserNameValidator(v); err != nil { + return &ValidationError{Name: "user_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_name": %w`, err)} + } + } + if v, ok := _u.mutation.RechargeCode(); ok { + if err := paymentorder.RechargeCodeValidator(v); err != nil { + return &ValidationError{Name: "recharge_code", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.recharge_code": %w`, err)} + } + } + if v, ok := _u.mutation.OutTradeNo(); ok { + if err := paymentorder.OutTradeNoValidator(v); err != nil { + return &ValidationError{Name: "out_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.out_trade_no": %w`, err)} + } + } + if v, ok := _u.mutation.PaymentType(); ok { + if err := paymentorder.PaymentTypeValidator(v); err != nil { + return &ValidationError{Name: "payment_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_type": %w`, err)} + } + } + if v, ok := _u.mutation.PaymentTradeNo(); ok { + if err := paymentorder.PaymentTradeNoValidator(v); err != nil { + return &ValidationError{Name: "payment_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_trade_no": %w`, err)} + } + } + if v, ok := _u.mutation.OrderType(); ok { + if err := paymentorder.OrderTypeValidator(v); err != nil { + return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)} + } + } + if v, ok := _u.mutation.ProviderInstanceID(); ok { + if err := paymentorder.ProviderInstanceIDValidator(v); err != nil { + return &ValidationError{Name: "provider_instance_id", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.provider_instance_id": %w`, err)} + } + } + if v, ok := _u.mutation.Status(); ok { + if err := paymentorder.StatusValidator(v); err != nil { + return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)} + } + } + if v, ok := _u.mutation.RefundRequestedBy(); ok { + if err := paymentorder.RefundRequestedByValidator(v); err != nil { + return &ValidationError{Name: "refund_requested_by", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_requested_by": %w`, err)} + } + } + if v, ok := _u.mutation.ClientIP(); ok { + if err := paymentorder.ClientIPValidator(v); err != nil { + return &ValidationError{Name: "client_ip", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.client_ip": %w`, err)} + } + } + if v, ok := _u.mutation.SrcHost(); ok { + if err := paymentorder.SrcHostValidator(v); err != nil { + return &ValidationError{Name: "src_host", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.src_host": %w`, err)} + } + } + if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "PaymentOrder.user"`) + } + return nil +} + +func (_u *PaymentOrderUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.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.UserEmail(); ok { + _spec.SetField(paymentorder.FieldUserEmail, field.TypeString, value) + } + if value, ok := _u.mutation.UserName(); ok { + _spec.SetField(paymentorder.FieldUserName, field.TypeString, value) + } + if value, ok := _u.mutation.UserNotes(); ok { + _spec.SetField(paymentorder.FieldUserNotes, field.TypeString, value) + } + if _u.mutation.UserNotesCleared() { + _spec.ClearField(paymentorder.FieldUserNotes, field.TypeString) + } + if value, ok := _u.mutation.Amount(); ok { + _spec.SetField(paymentorder.FieldAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedAmount(); ok { + _spec.AddField(paymentorder.FieldAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.PayAmount(); ok { + _spec.SetField(paymentorder.FieldPayAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedPayAmount(); ok { + _spec.AddField(paymentorder.FieldPayAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.FeeRate(); ok { + _spec.SetField(paymentorder.FieldFeeRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFeeRate(); ok { + _spec.AddField(paymentorder.FieldFeeRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.RechargeCode(); ok { + _spec.SetField(paymentorder.FieldRechargeCode, field.TypeString, value) + } + if value, ok := _u.mutation.OutTradeNo(); ok { + _spec.SetField(paymentorder.FieldOutTradeNo, field.TypeString, value) + } + if value, ok := _u.mutation.PaymentType(); ok { + _spec.SetField(paymentorder.FieldPaymentType, field.TypeString, value) + } + if value, ok := _u.mutation.PaymentTradeNo(); ok { + _spec.SetField(paymentorder.FieldPaymentTradeNo, field.TypeString, value) + } + if value, ok := _u.mutation.PayURL(); ok { + _spec.SetField(paymentorder.FieldPayURL, field.TypeString, value) + } + if _u.mutation.PayURLCleared() { + _spec.ClearField(paymentorder.FieldPayURL, field.TypeString) + } + if value, ok := _u.mutation.QrCode(); ok { + _spec.SetField(paymentorder.FieldQrCode, field.TypeString, value) + } + if _u.mutation.QrCodeCleared() { + _spec.ClearField(paymentorder.FieldQrCode, field.TypeString) + } + if value, ok := _u.mutation.QrCodeImg(); ok { + _spec.SetField(paymentorder.FieldQrCodeImg, field.TypeString, value) + } + if _u.mutation.QrCodeImgCleared() { + _spec.ClearField(paymentorder.FieldQrCodeImg, field.TypeString) + } + if value, ok := _u.mutation.OrderType(); ok { + _spec.SetField(paymentorder.FieldOrderType, field.TypeString, value) + } + if value, ok := _u.mutation.PlanID(); ok { + _spec.SetField(paymentorder.FieldPlanID, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedPlanID(); ok { + _spec.AddField(paymentorder.FieldPlanID, field.TypeInt64, value) + } + if _u.mutation.PlanIDCleared() { + _spec.ClearField(paymentorder.FieldPlanID, field.TypeInt64) + } + if value, ok := _u.mutation.SubscriptionGroupID(); ok { + _spec.SetField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedSubscriptionGroupID(); ok { + _spec.AddField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value) + } + if _u.mutation.SubscriptionGroupIDCleared() { + _spec.ClearField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64) + } + if value, ok := _u.mutation.SubscriptionDays(); ok { + _spec.SetField(paymentorder.FieldSubscriptionDays, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSubscriptionDays(); ok { + _spec.AddField(paymentorder.FieldSubscriptionDays, field.TypeInt, value) + } + if _u.mutation.SubscriptionDaysCleared() { + _spec.ClearField(paymentorder.FieldSubscriptionDays, field.TypeInt) + } + if value, ok := _u.mutation.ProviderInstanceID(); ok { + _spec.SetField(paymentorder.FieldProviderInstanceID, field.TypeString, value) + } + if _u.mutation.ProviderInstanceIDCleared() { + _spec.ClearField(paymentorder.FieldProviderInstanceID, field.TypeString) + } + if value, ok := _u.mutation.Status(); ok { + _spec.SetField(paymentorder.FieldStatus, field.TypeString, value) + } + if value, ok := _u.mutation.RefundAmount(); ok { + _spec.SetField(paymentorder.FieldRefundAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedRefundAmount(); ok { + _spec.AddField(paymentorder.FieldRefundAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.RefundReason(); ok { + _spec.SetField(paymentorder.FieldRefundReason, field.TypeString, value) + } + if _u.mutation.RefundReasonCleared() { + _spec.ClearField(paymentorder.FieldRefundReason, field.TypeString) + } + if value, ok := _u.mutation.RefundAt(); ok { + _spec.SetField(paymentorder.FieldRefundAt, field.TypeTime, value) + } + if _u.mutation.RefundAtCleared() { + _spec.ClearField(paymentorder.FieldRefundAt, field.TypeTime) + } + if value, ok := _u.mutation.ForceRefund(); ok { + _spec.SetField(paymentorder.FieldForceRefund, field.TypeBool, value) + } + if value, ok := _u.mutation.RefundRequestedAt(); ok { + _spec.SetField(paymentorder.FieldRefundRequestedAt, field.TypeTime, value) + } + if _u.mutation.RefundRequestedAtCleared() { + _spec.ClearField(paymentorder.FieldRefundRequestedAt, field.TypeTime) + } + if value, ok := _u.mutation.RefundRequestReason(); ok { + _spec.SetField(paymentorder.FieldRefundRequestReason, field.TypeString, value) + } + if _u.mutation.RefundRequestReasonCleared() { + _spec.ClearField(paymentorder.FieldRefundRequestReason, field.TypeString) + } + if value, ok := _u.mutation.RefundRequestedBy(); ok { + _spec.SetField(paymentorder.FieldRefundRequestedBy, field.TypeString, value) + } + if _u.mutation.RefundRequestedByCleared() { + _spec.ClearField(paymentorder.FieldRefundRequestedBy, field.TypeString) + } + if value, ok := _u.mutation.ExpiresAt(); ok { + _spec.SetField(paymentorder.FieldExpiresAt, field.TypeTime, value) + } + if value, ok := _u.mutation.PaidAt(); ok { + _spec.SetField(paymentorder.FieldPaidAt, field.TypeTime, value) + } + if _u.mutation.PaidAtCleared() { + _spec.ClearField(paymentorder.FieldPaidAt, field.TypeTime) + } + if value, ok := _u.mutation.CompletedAt(); ok { + _spec.SetField(paymentorder.FieldCompletedAt, field.TypeTime, value) + } + if _u.mutation.CompletedAtCleared() { + _spec.ClearField(paymentorder.FieldCompletedAt, field.TypeTime) + } + if value, ok := _u.mutation.FailedAt(); ok { + _spec.SetField(paymentorder.FieldFailedAt, field.TypeTime, value) + } + if _u.mutation.FailedAtCleared() { + _spec.ClearField(paymentorder.FieldFailedAt, field.TypeTime) + } + if value, ok := _u.mutation.FailedReason(); ok { + _spec.SetField(paymentorder.FieldFailedReason, field.TypeString, value) + } + if _u.mutation.FailedReasonCleared() { + _spec.ClearField(paymentorder.FieldFailedReason, field.TypeString) + } + if value, ok := _u.mutation.ClientIP(); ok { + _spec.SetField(paymentorder.FieldClientIP, field.TypeString, value) + } + if value, ok := _u.mutation.SrcHost(); ok { + _spec.SetField(paymentorder.FieldSrcHost, field.TypeString, value) + } + if value, ok := _u.mutation.SrcURL(); ok { + _spec.SetField(paymentorder.FieldSrcURL, field.TypeString, value) + } + if _u.mutation.SrcURLCleared() { + _spec.ClearField(paymentorder.FieldSrcURL, field.TypeString) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(paymentorder.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: paymentorder.UserTable, + Columns: []string{paymentorder.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: paymentorder.UserTable, + Columns: []string{paymentorder.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{paymentorder.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// PaymentOrderUpdateOne is the builder for updating a single PaymentOrder entity. +type PaymentOrderUpdateOne struct { + config + fields []string + hooks []Hook + mutation *PaymentOrderMutation +} + +// SetUserID sets the "user_id" field. +func (_u *PaymentOrderUpdateOne) SetUserID(v int64) *PaymentOrderUpdateOne { + _u.mutation.SetUserID(v) + return _u +} + +// SetNillableUserID sets the "user_id" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableUserID(v *int64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetUserID(*v) + } + return _u +} + +// SetUserEmail sets the "user_email" field. +func (_u *PaymentOrderUpdateOne) SetUserEmail(v string) *PaymentOrderUpdateOne { + _u.mutation.SetUserEmail(v) + return _u +} + +// SetNillableUserEmail sets the "user_email" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableUserEmail(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetUserEmail(*v) + } + return _u +} + +// SetUserName sets the "user_name" field. +func (_u *PaymentOrderUpdateOne) SetUserName(v string) *PaymentOrderUpdateOne { + _u.mutation.SetUserName(v) + return _u +} + +// SetNillableUserName sets the "user_name" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableUserName(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetUserName(*v) + } + return _u +} + +// SetUserNotes sets the "user_notes" field. +func (_u *PaymentOrderUpdateOne) SetUserNotes(v string) *PaymentOrderUpdateOne { + _u.mutation.SetUserNotes(v) + return _u +} + +// SetNillableUserNotes sets the "user_notes" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableUserNotes(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetUserNotes(*v) + } + return _u +} + +// ClearUserNotes clears the value of the "user_notes" field. +func (_u *PaymentOrderUpdateOne) ClearUserNotes() *PaymentOrderUpdateOne { + _u.mutation.ClearUserNotes() + return _u +} + +// SetAmount sets the "amount" field. +func (_u *PaymentOrderUpdateOne) SetAmount(v float64) *PaymentOrderUpdateOne { + _u.mutation.ResetAmount() + _u.mutation.SetAmount(v) + return _u +} + +// SetNillableAmount sets the "amount" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableAmount(v *float64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetAmount(*v) + } + return _u +} + +// AddAmount adds value to the "amount" field. +func (_u *PaymentOrderUpdateOne) AddAmount(v float64) *PaymentOrderUpdateOne { + _u.mutation.AddAmount(v) + return _u +} + +// SetPayAmount sets the "pay_amount" field. +func (_u *PaymentOrderUpdateOne) SetPayAmount(v float64) *PaymentOrderUpdateOne { + _u.mutation.ResetPayAmount() + _u.mutation.SetPayAmount(v) + return _u +} + +// SetNillablePayAmount sets the "pay_amount" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillablePayAmount(v *float64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetPayAmount(*v) + } + return _u +} + +// AddPayAmount adds value to the "pay_amount" field. +func (_u *PaymentOrderUpdateOne) AddPayAmount(v float64) *PaymentOrderUpdateOne { + _u.mutation.AddPayAmount(v) + return _u +} + +// SetFeeRate sets the "fee_rate" field. +func (_u *PaymentOrderUpdateOne) SetFeeRate(v float64) *PaymentOrderUpdateOne { + _u.mutation.ResetFeeRate() + _u.mutation.SetFeeRate(v) + return _u +} + +// SetNillableFeeRate sets the "fee_rate" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableFeeRate(v *float64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetFeeRate(*v) + } + return _u +} + +// AddFeeRate adds value to the "fee_rate" field. +func (_u *PaymentOrderUpdateOne) AddFeeRate(v float64) *PaymentOrderUpdateOne { + _u.mutation.AddFeeRate(v) + return _u +} + +// SetRechargeCode sets the "recharge_code" field. +func (_u *PaymentOrderUpdateOne) SetRechargeCode(v string) *PaymentOrderUpdateOne { + _u.mutation.SetRechargeCode(v) + return _u +} + +// SetNillableRechargeCode sets the "recharge_code" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRechargeCode(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRechargeCode(*v) + } + return _u +} + +// SetOutTradeNo sets the "out_trade_no" field. +func (_u *PaymentOrderUpdateOne) SetOutTradeNo(v string) *PaymentOrderUpdateOne { + _u.mutation.SetOutTradeNo(v) + return _u +} + +// SetNillableOutTradeNo sets the "out_trade_no" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableOutTradeNo(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetOutTradeNo(*v) + } + return _u +} + +// SetPaymentType sets the "payment_type" field. +func (_u *PaymentOrderUpdateOne) SetPaymentType(v string) *PaymentOrderUpdateOne { + _u.mutation.SetPaymentType(v) + return _u +} + +// SetNillablePaymentType sets the "payment_type" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillablePaymentType(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetPaymentType(*v) + } + return _u +} + +// SetPaymentTradeNo sets the "payment_trade_no" field. +func (_u *PaymentOrderUpdateOne) SetPaymentTradeNo(v string) *PaymentOrderUpdateOne { + _u.mutation.SetPaymentTradeNo(v) + return _u +} + +// SetNillablePaymentTradeNo sets the "payment_trade_no" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillablePaymentTradeNo(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetPaymentTradeNo(*v) + } + return _u +} + +// SetPayURL sets the "pay_url" field. +func (_u *PaymentOrderUpdateOne) SetPayURL(v string) *PaymentOrderUpdateOne { + _u.mutation.SetPayURL(v) + return _u +} + +// SetNillablePayURL sets the "pay_url" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillablePayURL(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetPayURL(*v) + } + return _u +} + +// ClearPayURL clears the value of the "pay_url" field. +func (_u *PaymentOrderUpdateOne) ClearPayURL() *PaymentOrderUpdateOne { + _u.mutation.ClearPayURL() + return _u +} + +// SetQrCode sets the "qr_code" field. +func (_u *PaymentOrderUpdateOne) SetQrCode(v string) *PaymentOrderUpdateOne { + _u.mutation.SetQrCode(v) + return _u +} + +// SetNillableQrCode sets the "qr_code" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableQrCode(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetQrCode(*v) + } + return _u +} + +// ClearQrCode clears the value of the "qr_code" field. +func (_u *PaymentOrderUpdateOne) ClearQrCode() *PaymentOrderUpdateOne { + _u.mutation.ClearQrCode() + return _u +} + +// SetQrCodeImg sets the "qr_code_img" field. +func (_u *PaymentOrderUpdateOne) SetQrCodeImg(v string) *PaymentOrderUpdateOne { + _u.mutation.SetQrCodeImg(v) + return _u +} + +// SetNillableQrCodeImg sets the "qr_code_img" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableQrCodeImg(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetQrCodeImg(*v) + } + return _u +} + +// ClearQrCodeImg clears the value of the "qr_code_img" field. +func (_u *PaymentOrderUpdateOne) ClearQrCodeImg() *PaymentOrderUpdateOne { + _u.mutation.ClearQrCodeImg() + return _u +} + +// SetOrderType sets the "order_type" field. +func (_u *PaymentOrderUpdateOne) SetOrderType(v string) *PaymentOrderUpdateOne { + _u.mutation.SetOrderType(v) + return _u +} + +// SetNillableOrderType sets the "order_type" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableOrderType(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetOrderType(*v) + } + return _u +} + +// SetPlanID sets the "plan_id" field. +func (_u *PaymentOrderUpdateOne) SetPlanID(v int64) *PaymentOrderUpdateOne { + _u.mutation.ResetPlanID() + _u.mutation.SetPlanID(v) + return _u +} + +// SetNillablePlanID sets the "plan_id" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillablePlanID(v *int64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetPlanID(*v) + } + return _u +} + +// AddPlanID adds value to the "plan_id" field. +func (_u *PaymentOrderUpdateOne) AddPlanID(v int64) *PaymentOrderUpdateOne { + _u.mutation.AddPlanID(v) + return _u +} + +// ClearPlanID clears the value of the "plan_id" field. +func (_u *PaymentOrderUpdateOne) ClearPlanID() *PaymentOrderUpdateOne { + _u.mutation.ClearPlanID() + return _u +} + +// SetSubscriptionGroupID sets the "subscription_group_id" field. +func (_u *PaymentOrderUpdateOne) SetSubscriptionGroupID(v int64) *PaymentOrderUpdateOne { + _u.mutation.ResetSubscriptionGroupID() + _u.mutation.SetSubscriptionGroupID(v) + return _u +} + +// SetNillableSubscriptionGroupID sets the "subscription_group_id" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableSubscriptionGroupID(v *int64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetSubscriptionGroupID(*v) + } + return _u +} + +// AddSubscriptionGroupID adds value to the "subscription_group_id" field. +func (_u *PaymentOrderUpdateOne) AddSubscriptionGroupID(v int64) *PaymentOrderUpdateOne { + _u.mutation.AddSubscriptionGroupID(v) + return _u +} + +// ClearSubscriptionGroupID clears the value of the "subscription_group_id" field. +func (_u *PaymentOrderUpdateOne) ClearSubscriptionGroupID() *PaymentOrderUpdateOne { + _u.mutation.ClearSubscriptionGroupID() + return _u +} + +// SetSubscriptionDays sets the "subscription_days" field. +func (_u *PaymentOrderUpdateOne) SetSubscriptionDays(v int) *PaymentOrderUpdateOne { + _u.mutation.ResetSubscriptionDays() + _u.mutation.SetSubscriptionDays(v) + return _u +} + +// SetNillableSubscriptionDays sets the "subscription_days" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableSubscriptionDays(v *int) *PaymentOrderUpdateOne { + if v != nil { + _u.SetSubscriptionDays(*v) + } + return _u +} + +// AddSubscriptionDays adds value to the "subscription_days" field. +func (_u *PaymentOrderUpdateOne) AddSubscriptionDays(v int) *PaymentOrderUpdateOne { + _u.mutation.AddSubscriptionDays(v) + return _u +} + +// ClearSubscriptionDays clears the value of the "subscription_days" field. +func (_u *PaymentOrderUpdateOne) ClearSubscriptionDays() *PaymentOrderUpdateOne { + _u.mutation.ClearSubscriptionDays() + return _u +} + +// SetProviderInstanceID sets the "provider_instance_id" field. +func (_u *PaymentOrderUpdateOne) SetProviderInstanceID(v string) *PaymentOrderUpdateOne { + _u.mutation.SetProviderInstanceID(v) + return _u +} + +// SetNillableProviderInstanceID sets the "provider_instance_id" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableProviderInstanceID(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetProviderInstanceID(*v) + } + return _u +} + +// ClearProviderInstanceID clears the value of the "provider_instance_id" field. +func (_u *PaymentOrderUpdateOne) ClearProviderInstanceID() *PaymentOrderUpdateOne { + _u.mutation.ClearProviderInstanceID() + return _u +} + +// SetStatus sets the "status" field. +func (_u *PaymentOrderUpdateOne) SetStatus(v string) *PaymentOrderUpdateOne { + _u.mutation.SetStatus(v) + return _u +} + +// SetNillableStatus sets the "status" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableStatus(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetStatus(*v) + } + return _u +} + +// SetRefundAmount sets the "refund_amount" field. +func (_u *PaymentOrderUpdateOne) SetRefundAmount(v float64) *PaymentOrderUpdateOne { + _u.mutation.ResetRefundAmount() + _u.mutation.SetRefundAmount(v) + return _u +} + +// SetNillableRefundAmount sets the "refund_amount" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundAmount(v *float64) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRefundAmount(*v) + } + return _u +} + +// AddRefundAmount adds value to the "refund_amount" field. +func (_u *PaymentOrderUpdateOne) AddRefundAmount(v float64) *PaymentOrderUpdateOne { + _u.mutation.AddRefundAmount(v) + return _u +} + +// SetRefundReason sets the "refund_reason" field. +func (_u *PaymentOrderUpdateOne) SetRefundReason(v string) *PaymentOrderUpdateOne { + _u.mutation.SetRefundReason(v) + return _u +} + +// SetNillableRefundReason sets the "refund_reason" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundReason(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRefundReason(*v) + } + return _u +} + +// ClearRefundReason clears the value of the "refund_reason" field. +func (_u *PaymentOrderUpdateOne) ClearRefundReason() *PaymentOrderUpdateOne { + _u.mutation.ClearRefundReason() + return _u +} + +// SetRefundAt sets the "refund_at" field. +func (_u *PaymentOrderUpdateOne) SetRefundAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetRefundAt(v) + return _u +} + +// SetNillableRefundAt sets the "refund_at" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundAt(v *time.Time) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRefundAt(*v) + } + return _u +} + +// ClearRefundAt clears the value of the "refund_at" field. +func (_u *PaymentOrderUpdateOne) ClearRefundAt() *PaymentOrderUpdateOne { + _u.mutation.ClearRefundAt() + return _u +} + +// SetForceRefund sets the "force_refund" field. +func (_u *PaymentOrderUpdateOne) SetForceRefund(v bool) *PaymentOrderUpdateOne { + _u.mutation.SetForceRefund(v) + return _u +} + +// SetNillableForceRefund sets the "force_refund" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableForceRefund(v *bool) *PaymentOrderUpdateOne { + if v != nil { + _u.SetForceRefund(*v) + } + return _u +} + +// SetRefundRequestedAt sets the "refund_requested_at" field. +func (_u *PaymentOrderUpdateOne) SetRefundRequestedAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetRefundRequestedAt(v) + return _u +} + +// SetNillableRefundRequestedAt sets the "refund_requested_at" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundRequestedAt(v *time.Time) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRefundRequestedAt(*v) + } + return _u +} + +// ClearRefundRequestedAt clears the value of the "refund_requested_at" field. +func (_u *PaymentOrderUpdateOne) ClearRefundRequestedAt() *PaymentOrderUpdateOne { + _u.mutation.ClearRefundRequestedAt() + return _u +} + +// SetRefundRequestReason sets the "refund_request_reason" field. +func (_u *PaymentOrderUpdateOne) SetRefundRequestReason(v string) *PaymentOrderUpdateOne { + _u.mutation.SetRefundRequestReason(v) + return _u +} + +// SetNillableRefundRequestReason sets the "refund_request_reason" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundRequestReason(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRefundRequestReason(*v) + } + return _u +} + +// ClearRefundRequestReason clears the value of the "refund_request_reason" field. +func (_u *PaymentOrderUpdateOne) ClearRefundRequestReason() *PaymentOrderUpdateOne { + _u.mutation.ClearRefundRequestReason() + return _u +} + +// SetRefundRequestedBy sets the "refund_requested_by" field. +func (_u *PaymentOrderUpdateOne) SetRefundRequestedBy(v string) *PaymentOrderUpdateOne { + _u.mutation.SetRefundRequestedBy(v) + return _u +} + +// SetNillableRefundRequestedBy sets the "refund_requested_by" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundRequestedBy(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetRefundRequestedBy(*v) + } + return _u +} + +// ClearRefundRequestedBy clears the value of the "refund_requested_by" field. +func (_u *PaymentOrderUpdateOne) ClearRefundRequestedBy() *PaymentOrderUpdateOne { + _u.mutation.ClearRefundRequestedBy() + return _u +} + +// SetExpiresAt sets the "expires_at" field. +func (_u *PaymentOrderUpdateOne) SetExpiresAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetExpiresAt(v) + return _u +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableExpiresAt(v *time.Time) *PaymentOrderUpdateOne { + if v != nil { + _u.SetExpiresAt(*v) + } + return _u +} + +// SetPaidAt sets the "paid_at" field. +func (_u *PaymentOrderUpdateOne) SetPaidAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetPaidAt(v) + return _u +} + +// SetNillablePaidAt sets the "paid_at" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillablePaidAt(v *time.Time) *PaymentOrderUpdateOne { + if v != nil { + _u.SetPaidAt(*v) + } + return _u +} + +// ClearPaidAt clears the value of the "paid_at" field. +func (_u *PaymentOrderUpdateOne) ClearPaidAt() *PaymentOrderUpdateOne { + _u.mutation.ClearPaidAt() + return _u +} + +// SetCompletedAt sets the "completed_at" field. +func (_u *PaymentOrderUpdateOne) SetCompletedAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetCompletedAt(v) + return _u +} + +// SetNillableCompletedAt sets the "completed_at" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableCompletedAt(v *time.Time) *PaymentOrderUpdateOne { + if v != nil { + _u.SetCompletedAt(*v) + } + return _u +} + +// ClearCompletedAt clears the value of the "completed_at" field. +func (_u *PaymentOrderUpdateOne) ClearCompletedAt() *PaymentOrderUpdateOne { + _u.mutation.ClearCompletedAt() + return _u +} + +// SetFailedAt sets the "failed_at" field. +func (_u *PaymentOrderUpdateOne) SetFailedAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetFailedAt(v) + return _u +} + +// SetNillableFailedAt sets the "failed_at" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableFailedAt(v *time.Time) *PaymentOrderUpdateOne { + if v != nil { + _u.SetFailedAt(*v) + } + return _u +} + +// ClearFailedAt clears the value of the "failed_at" field. +func (_u *PaymentOrderUpdateOne) ClearFailedAt() *PaymentOrderUpdateOne { + _u.mutation.ClearFailedAt() + return _u +} + +// SetFailedReason sets the "failed_reason" field. +func (_u *PaymentOrderUpdateOne) SetFailedReason(v string) *PaymentOrderUpdateOne { + _u.mutation.SetFailedReason(v) + return _u +} + +// SetNillableFailedReason sets the "failed_reason" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableFailedReason(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetFailedReason(*v) + } + return _u +} + +// ClearFailedReason clears the value of the "failed_reason" field. +func (_u *PaymentOrderUpdateOne) ClearFailedReason() *PaymentOrderUpdateOne { + _u.mutation.ClearFailedReason() + return _u +} + +// SetClientIP sets the "client_ip" field. +func (_u *PaymentOrderUpdateOne) SetClientIP(v string) *PaymentOrderUpdateOne { + _u.mutation.SetClientIP(v) + return _u +} + +// SetNillableClientIP sets the "client_ip" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableClientIP(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetClientIP(*v) + } + return _u +} + +// SetSrcHost sets the "src_host" field. +func (_u *PaymentOrderUpdateOne) SetSrcHost(v string) *PaymentOrderUpdateOne { + _u.mutation.SetSrcHost(v) + return _u +} + +// SetNillableSrcHost sets the "src_host" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableSrcHost(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetSrcHost(*v) + } + return _u +} + +// SetSrcURL sets the "src_url" field. +func (_u *PaymentOrderUpdateOne) SetSrcURL(v string) *PaymentOrderUpdateOne { + _u.mutation.SetSrcURL(v) + return _u +} + +// SetNillableSrcURL sets the "src_url" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableSrcURL(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetSrcURL(*v) + } + return _u +} + +// ClearSrcURL clears the value of the "src_url" field. +func (_u *PaymentOrderUpdateOne) ClearSrcURL() *PaymentOrderUpdateOne { + _u.mutation.ClearSrcURL() + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *PaymentOrderUpdateOne) SetUpdatedAt(v time.Time) *PaymentOrderUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetUser sets the "user" edge to the User entity. +func (_u *PaymentOrderUpdateOne) SetUser(v *User) *PaymentOrderUpdateOne { + return _u.SetUserID(v.ID) +} + +// Mutation returns the PaymentOrderMutation object of the builder. +func (_u *PaymentOrderUpdateOne) Mutation() *PaymentOrderMutation { + return _u.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (_u *PaymentOrderUpdateOne) ClearUser() *PaymentOrderUpdateOne { + _u.mutation.ClearUser() + return _u +} + +// Where appends a list predicates to the PaymentOrderUpdate builder. +func (_u *PaymentOrderUpdateOne) Where(ps ...predicate.PaymentOrder) *PaymentOrderUpdateOne { + _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 *PaymentOrderUpdateOne) Select(field string, fields ...string) *PaymentOrderUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated PaymentOrder entity. +func (_u *PaymentOrderUpdateOne) Save(ctx context.Context) (*PaymentOrder, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *PaymentOrderUpdateOne) SaveX(ctx context.Context) *PaymentOrder { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *PaymentOrderUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *PaymentOrderUpdateOne) 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 *PaymentOrderUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := paymentorder.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *PaymentOrderUpdateOne) check() error { + if v, ok := _u.mutation.UserEmail(); ok { + if err := paymentorder.UserEmailValidator(v); err != nil { + return &ValidationError{Name: "user_email", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_email": %w`, err)} + } + } + if v, ok := _u.mutation.UserName(); ok { + if err := paymentorder.UserNameValidator(v); err != nil { + return &ValidationError{Name: "user_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.user_name": %w`, err)} + } + } + if v, ok := _u.mutation.RechargeCode(); ok { + if err := paymentorder.RechargeCodeValidator(v); err != nil { + return &ValidationError{Name: "recharge_code", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.recharge_code": %w`, err)} + } + } + if v, ok := _u.mutation.OutTradeNo(); ok { + if err := paymentorder.OutTradeNoValidator(v); err != nil { + return &ValidationError{Name: "out_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.out_trade_no": %w`, err)} + } + } + if v, ok := _u.mutation.PaymentType(); ok { + if err := paymentorder.PaymentTypeValidator(v); err != nil { + return &ValidationError{Name: "payment_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_type": %w`, err)} + } + } + if v, ok := _u.mutation.PaymentTradeNo(); ok { + if err := paymentorder.PaymentTradeNoValidator(v); err != nil { + return &ValidationError{Name: "payment_trade_no", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.payment_trade_no": %w`, err)} + } + } + if v, ok := _u.mutation.OrderType(); ok { + if err := paymentorder.OrderTypeValidator(v); err != nil { + return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)} + } + } + if v, ok := _u.mutation.ProviderInstanceID(); ok { + if err := paymentorder.ProviderInstanceIDValidator(v); err != nil { + return &ValidationError{Name: "provider_instance_id", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.provider_instance_id": %w`, err)} + } + } + if v, ok := _u.mutation.Status(); ok { + if err := paymentorder.StatusValidator(v); err != nil { + return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)} + } + } + if v, ok := _u.mutation.RefundRequestedBy(); ok { + if err := paymentorder.RefundRequestedByValidator(v); err != nil { + return &ValidationError{Name: "refund_requested_by", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_requested_by": %w`, err)} + } + } + if v, ok := _u.mutation.ClientIP(); ok { + if err := paymentorder.ClientIPValidator(v); err != nil { + return &ValidationError{Name: "client_ip", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.client_ip": %w`, err)} + } + } + if v, ok := _u.mutation.SrcHost(); ok { + if err := paymentorder.SrcHostValidator(v); err != nil { + return &ValidationError{Name: "src_host", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.src_host": %w`, err)} + } + } + if _u.mutation.UserCleared() && len(_u.mutation.UserIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "PaymentOrder.user"`) + } + return nil +} + +func (_u *PaymentOrderUpdateOne) sqlSave(ctx context.Context) (_node *PaymentOrder, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentOrder.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, paymentorder.FieldID) + for _, f := range fields { + if !paymentorder.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != paymentorder.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.UserEmail(); ok { + _spec.SetField(paymentorder.FieldUserEmail, field.TypeString, value) + } + if value, ok := _u.mutation.UserName(); ok { + _spec.SetField(paymentorder.FieldUserName, field.TypeString, value) + } + if value, ok := _u.mutation.UserNotes(); ok { + _spec.SetField(paymentorder.FieldUserNotes, field.TypeString, value) + } + if _u.mutation.UserNotesCleared() { + _spec.ClearField(paymentorder.FieldUserNotes, field.TypeString) + } + if value, ok := _u.mutation.Amount(); ok { + _spec.SetField(paymentorder.FieldAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedAmount(); ok { + _spec.AddField(paymentorder.FieldAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.PayAmount(); ok { + _spec.SetField(paymentorder.FieldPayAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedPayAmount(); ok { + _spec.AddField(paymentorder.FieldPayAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.FeeRate(); ok { + _spec.SetField(paymentorder.FieldFeeRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFeeRate(); ok { + _spec.AddField(paymentorder.FieldFeeRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.RechargeCode(); ok { + _spec.SetField(paymentorder.FieldRechargeCode, field.TypeString, value) + } + if value, ok := _u.mutation.OutTradeNo(); ok { + _spec.SetField(paymentorder.FieldOutTradeNo, field.TypeString, value) + } + if value, ok := _u.mutation.PaymentType(); ok { + _spec.SetField(paymentorder.FieldPaymentType, field.TypeString, value) + } + if value, ok := _u.mutation.PaymentTradeNo(); ok { + _spec.SetField(paymentorder.FieldPaymentTradeNo, field.TypeString, value) + } + if value, ok := _u.mutation.PayURL(); ok { + _spec.SetField(paymentorder.FieldPayURL, field.TypeString, value) + } + if _u.mutation.PayURLCleared() { + _spec.ClearField(paymentorder.FieldPayURL, field.TypeString) + } + if value, ok := _u.mutation.QrCode(); ok { + _spec.SetField(paymentorder.FieldQrCode, field.TypeString, value) + } + if _u.mutation.QrCodeCleared() { + _spec.ClearField(paymentorder.FieldQrCode, field.TypeString) + } + if value, ok := _u.mutation.QrCodeImg(); ok { + _spec.SetField(paymentorder.FieldQrCodeImg, field.TypeString, value) + } + if _u.mutation.QrCodeImgCleared() { + _spec.ClearField(paymentorder.FieldQrCodeImg, field.TypeString) + } + if value, ok := _u.mutation.OrderType(); ok { + _spec.SetField(paymentorder.FieldOrderType, field.TypeString, value) + } + if value, ok := _u.mutation.PlanID(); ok { + _spec.SetField(paymentorder.FieldPlanID, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedPlanID(); ok { + _spec.AddField(paymentorder.FieldPlanID, field.TypeInt64, value) + } + if _u.mutation.PlanIDCleared() { + _spec.ClearField(paymentorder.FieldPlanID, field.TypeInt64) + } + if value, ok := _u.mutation.SubscriptionGroupID(); ok { + _spec.SetField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedSubscriptionGroupID(); ok { + _spec.AddField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64, value) + } + if _u.mutation.SubscriptionGroupIDCleared() { + _spec.ClearField(paymentorder.FieldSubscriptionGroupID, field.TypeInt64) + } + if value, ok := _u.mutation.SubscriptionDays(); ok { + _spec.SetField(paymentorder.FieldSubscriptionDays, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSubscriptionDays(); ok { + _spec.AddField(paymentorder.FieldSubscriptionDays, field.TypeInt, value) + } + if _u.mutation.SubscriptionDaysCleared() { + _spec.ClearField(paymentorder.FieldSubscriptionDays, field.TypeInt) + } + if value, ok := _u.mutation.ProviderInstanceID(); ok { + _spec.SetField(paymentorder.FieldProviderInstanceID, field.TypeString, value) + } + if _u.mutation.ProviderInstanceIDCleared() { + _spec.ClearField(paymentorder.FieldProviderInstanceID, field.TypeString) + } + if value, ok := _u.mutation.Status(); ok { + _spec.SetField(paymentorder.FieldStatus, field.TypeString, value) + } + if value, ok := _u.mutation.RefundAmount(); ok { + _spec.SetField(paymentorder.FieldRefundAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedRefundAmount(); ok { + _spec.AddField(paymentorder.FieldRefundAmount, field.TypeFloat64, value) + } + if value, ok := _u.mutation.RefundReason(); ok { + _spec.SetField(paymentorder.FieldRefundReason, field.TypeString, value) + } + if _u.mutation.RefundReasonCleared() { + _spec.ClearField(paymentorder.FieldRefundReason, field.TypeString) + } + if value, ok := _u.mutation.RefundAt(); ok { + _spec.SetField(paymentorder.FieldRefundAt, field.TypeTime, value) + } + if _u.mutation.RefundAtCleared() { + _spec.ClearField(paymentorder.FieldRefundAt, field.TypeTime) + } + if value, ok := _u.mutation.ForceRefund(); ok { + _spec.SetField(paymentorder.FieldForceRefund, field.TypeBool, value) + } + if value, ok := _u.mutation.RefundRequestedAt(); ok { + _spec.SetField(paymentorder.FieldRefundRequestedAt, field.TypeTime, value) + } + if _u.mutation.RefundRequestedAtCleared() { + _spec.ClearField(paymentorder.FieldRefundRequestedAt, field.TypeTime) + } + if value, ok := _u.mutation.RefundRequestReason(); ok { + _spec.SetField(paymentorder.FieldRefundRequestReason, field.TypeString, value) + } + if _u.mutation.RefundRequestReasonCleared() { + _spec.ClearField(paymentorder.FieldRefundRequestReason, field.TypeString) + } + if value, ok := _u.mutation.RefundRequestedBy(); ok { + _spec.SetField(paymentorder.FieldRefundRequestedBy, field.TypeString, value) + } + if _u.mutation.RefundRequestedByCleared() { + _spec.ClearField(paymentorder.FieldRefundRequestedBy, field.TypeString) + } + if value, ok := _u.mutation.ExpiresAt(); ok { + _spec.SetField(paymentorder.FieldExpiresAt, field.TypeTime, value) + } + if value, ok := _u.mutation.PaidAt(); ok { + _spec.SetField(paymentorder.FieldPaidAt, field.TypeTime, value) + } + if _u.mutation.PaidAtCleared() { + _spec.ClearField(paymentorder.FieldPaidAt, field.TypeTime) + } + if value, ok := _u.mutation.CompletedAt(); ok { + _spec.SetField(paymentorder.FieldCompletedAt, field.TypeTime, value) + } + if _u.mutation.CompletedAtCleared() { + _spec.ClearField(paymentorder.FieldCompletedAt, field.TypeTime) + } + if value, ok := _u.mutation.FailedAt(); ok { + _spec.SetField(paymentorder.FieldFailedAt, field.TypeTime, value) + } + if _u.mutation.FailedAtCleared() { + _spec.ClearField(paymentorder.FieldFailedAt, field.TypeTime) + } + if value, ok := _u.mutation.FailedReason(); ok { + _spec.SetField(paymentorder.FieldFailedReason, field.TypeString, value) + } + if _u.mutation.FailedReasonCleared() { + _spec.ClearField(paymentorder.FieldFailedReason, field.TypeString) + } + if value, ok := _u.mutation.ClientIP(); ok { + _spec.SetField(paymentorder.FieldClientIP, field.TypeString, value) + } + if value, ok := _u.mutation.SrcHost(); ok { + _spec.SetField(paymentorder.FieldSrcHost, field.TypeString, value) + } + if value, ok := _u.mutation.SrcURL(); ok { + _spec.SetField(paymentorder.FieldSrcURL, field.TypeString, value) + } + if _u.mutation.SrcURLCleared() { + _spec.ClearField(paymentorder.FieldSrcURL, field.TypeString) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(paymentorder.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: paymentorder.UserTable, + Columns: []string{paymentorder.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: paymentorder.UserTable, + Columns: []string{paymentorder.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 = &PaymentOrder{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{paymentorder.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/paymentproviderinstance.go b/backend/ent/paymentproviderinstance.go new file mode 100644 index 0000000000000000000000000000000000000000..087cb13a2006507aecebe910e68d2bade1e3ca3e --- /dev/null +++ b/backend/ent/paymentproviderinstance.go @@ -0,0 +1,218 @@ +// 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/paymentproviderinstance" +) + +// PaymentProviderInstance is the model entity for the PaymentProviderInstance schema. +type PaymentProviderInstance struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // ProviderKey holds the value of the "provider_key" field. + ProviderKey string `json:"provider_key,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Config holds the value of the "config" field. + Config string `json:"config,omitempty"` + // SupportedTypes holds the value of the "supported_types" field. + SupportedTypes string `json:"supported_types,omitempty"` + // Enabled holds the value of the "enabled" field. + Enabled bool `json:"enabled,omitempty"` + // PaymentMode holds the value of the "payment_mode" field. + PaymentMode string `json:"payment_mode,omitempty"` + // SortOrder holds the value of the "sort_order" field. + SortOrder int `json:"sort_order,omitempty"` + // Limits holds the value of the "limits" field. + Limits string `json:"limits,omitempty"` + // RefundEnabled holds the value of the "refund_enabled" field. + RefundEnabled bool `json:"refund_enabled,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"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*PaymentProviderInstance) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case paymentproviderinstance.FieldEnabled, paymentproviderinstance.FieldRefundEnabled: + values[i] = new(sql.NullBool) + case paymentproviderinstance.FieldID, paymentproviderinstance.FieldSortOrder: + values[i] = new(sql.NullInt64) + case paymentproviderinstance.FieldProviderKey, paymentproviderinstance.FieldName, paymentproviderinstance.FieldConfig, paymentproviderinstance.FieldSupportedTypes, paymentproviderinstance.FieldPaymentMode, paymentproviderinstance.FieldLimits: + values[i] = new(sql.NullString) + case paymentproviderinstance.FieldCreatedAt, paymentproviderinstance.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 PaymentProviderInstance fields. +func (_m *PaymentProviderInstance) 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 paymentproviderinstance.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 paymentproviderinstance.FieldProviderKey: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field provider_key", values[i]) + } else if value.Valid { + _m.ProviderKey = value.String + } + case paymentproviderinstance.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case paymentproviderinstance.FieldConfig: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field config", values[i]) + } else if value.Valid { + _m.Config = value.String + } + case paymentproviderinstance.FieldSupportedTypes: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field supported_types", values[i]) + } else if value.Valid { + _m.SupportedTypes = value.String + } + case paymentproviderinstance.FieldEnabled: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field enabled", values[i]) + } else if value.Valid { + _m.Enabled = value.Bool + } + case paymentproviderinstance.FieldPaymentMode: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field payment_mode", values[i]) + } else if value.Valid { + _m.PaymentMode = value.String + } + case paymentproviderinstance.FieldSortOrder: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field sort_order", values[i]) + } else if value.Valid { + _m.SortOrder = int(value.Int64) + } + case paymentproviderinstance.FieldLimits: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field limits", values[i]) + } else if value.Valid { + _m.Limits = value.String + } + case paymentproviderinstance.FieldRefundEnabled: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field refund_enabled", values[i]) + } else if value.Valid { + _m.RefundEnabled = value.Bool + } + case paymentproviderinstance.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 paymentproviderinstance.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 PaymentProviderInstance. +// This includes values selected through modifiers, order, etc. +func (_m *PaymentProviderInstance) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this PaymentProviderInstance. +// Note that you need to call PaymentProviderInstance.Unwrap() before calling this method if this PaymentProviderInstance +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *PaymentProviderInstance) Update() *PaymentProviderInstanceUpdateOne { + return NewPaymentProviderInstanceClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the PaymentProviderInstance 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 *PaymentProviderInstance) Unwrap() *PaymentProviderInstance { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: PaymentProviderInstance is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *PaymentProviderInstance) String() string { + var builder strings.Builder + builder.WriteString("PaymentProviderInstance(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("provider_key=") + builder.WriteString(_m.ProviderKey) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + builder.WriteString("config=") + builder.WriteString(_m.Config) + builder.WriteString(", ") + builder.WriteString("supported_types=") + builder.WriteString(_m.SupportedTypes) + builder.WriteString(", ") + builder.WriteString("enabled=") + builder.WriteString(fmt.Sprintf("%v", _m.Enabled)) + builder.WriteString(", ") + builder.WriteString("payment_mode=") + builder.WriteString(_m.PaymentMode) + builder.WriteString(", ") + builder.WriteString("sort_order=") + builder.WriteString(fmt.Sprintf("%v", _m.SortOrder)) + builder.WriteString(", ") + builder.WriteString("limits=") + builder.WriteString(_m.Limits) + builder.WriteString(", ") + builder.WriteString("refund_enabled=") + builder.WriteString(fmt.Sprintf("%v", _m.RefundEnabled)) + 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() +} + +// PaymentProviderInstances is a parsable slice of PaymentProviderInstance. +type PaymentProviderInstances []*PaymentProviderInstance diff --git a/backend/ent/paymentproviderinstance/paymentproviderinstance.go b/backend/ent/paymentproviderinstance/paymentproviderinstance.go new file mode 100644 index 0000000000000000000000000000000000000000..c430fef6c3b42ed54782065763ac693561f3c2ef --- /dev/null +++ b/backend/ent/paymentproviderinstance/paymentproviderinstance.go @@ -0,0 +1,160 @@ +// Code generated by ent, DO NOT EDIT. + +package paymentproviderinstance + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the paymentproviderinstance type in the database. + Label = "payment_provider_instance" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldProviderKey holds the string denoting the provider_key field in the database. + FieldProviderKey = "provider_key" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldConfig holds the string denoting the config field in the database. + FieldConfig = "config" + // FieldSupportedTypes holds the string denoting the supported_types field in the database. + FieldSupportedTypes = "supported_types" + // FieldEnabled holds the string denoting the enabled field in the database. + FieldEnabled = "enabled" + // FieldPaymentMode holds the string denoting the payment_mode field in the database. + FieldPaymentMode = "payment_mode" + // FieldSortOrder holds the string denoting the sort_order field in the database. + FieldSortOrder = "sort_order" + // FieldLimits holds the string denoting the limits field in the database. + FieldLimits = "limits" + // FieldRefundEnabled holds the string denoting the refund_enabled field in the database. + FieldRefundEnabled = "refund_enabled" + // 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" + // Table holds the table name of the paymentproviderinstance in the database. + Table = "payment_provider_instances" +) + +// Columns holds all SQL columns for paymentproviderinstance fields. +var Columns = []string{ + FieldID, + FieldProviderKey, + FieldName, + FieldConfig, + FieldSupportedTypes, + FieldEnabled, + FieldPaymentMode, + FieldSortOrder, + FieldLimits, + FieldRefundEnabled, + 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 ( + // ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save. + ProviderKeyValidator func(string) error + // DefaultName holds the default value on creation for the "name" field. + DefaultName string + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // DefaultSupportedTypes holds the default value on creation for the "supported_types" field. + DefaultSupportedTypes string + // SupportedTypesValidator is a validator for the "supported_types" field. It is called by the builders before save. + SupportedTypesValidator func(string) error + // DefaultEnabled holds the default value on creation for the "enabled" field. + DefaultEnabled bool + // DefaultPaymentMode holds the default value on creation for the "payment_mode" field. + DefaultPaymentMode string + // PaymentModeValidator is a validator for the "payment_mode" field. It is called by the builders before save. + PaymentModeValidator func(string) error + // DefaultSortOrder holds the default value on creation for the "sort_order" field. + DefaultSortOrder int + // DefaultLimits holds the default value on creation for the "limits" field. + DefaultLimits string + // DefaultRefundEnabled holds the default value on creation for the "refund_enabled" field. + DefaultRefundEnabled bool + // 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 PaymentProviderInstance 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() +} + +// ByProviderKey orders the results by the provider_key field. +func ByProviderKey(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldProviderKey, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByConfig orders the results by the config field. +func ByConfig(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldConfig, opts...).ToFunc() +} + +// BySupportedTypes orders the results by the supported_types field. +func BySupportedTypes(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSupportedTypes, opts...).ToFunc() +} + +// ByEnabled orders the results by the enabled field. +func ByEnabled(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldEnabled, opts...).ToFunc() +} + +// ByPaymentMode orders the results by the payment_mode field. +func ByPaymentMode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPaymentMode, opts...).ToFunc() +} + +// BySortOrder orders the results by the sort_order field. +func BySortOrder(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSortOrder, opts...).ToFunc() +} + +// ByLimits orders the results by the limits field. +func ByLimits(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLimits, opts...).ToFunc() +} + +// ByRefundEnabled orders the results by the refund_enabled field. +func ByRefundEnabled(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundEnabled, 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() +} diff --git a/backend/ent/paymentproviderinstance/where.go b/backend/ent/paymentproviderinstance/where.go new file mode 100644 index 0000000000000000000000000000000000000000..7b99517f3ad94006f15e800f1b7729914ddee7ab --- /dev/null +++ b/backend/ent/paymentproviderinstance/where.go @@ -0,0 +1,655 @@ +// Code generated by ent, DO NOT EDIT. + +package paymentproviderinstance + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldID, id)) +} + +// ProviderKey applies equality check predicate on the "provider_key" field. It's identical to ProviderKeyEQ. +func ProviderKey(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldProviderKey, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldName, v)) +} + +// Config applies equality check predicate on the "config" field. It's identical to ConfigEQ. +func Config(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldConfig, v)) +} + +// SupportedTypes applies equality check predicate on the "supported_types" field. It's identical to SupportedTypesEQ. +func SupportedTypes(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSupportedTypes, v)) +} + +// Enabled applies equality check predicate on the "enabled" field. It's identical to EnabledEQ. +func Enabled(v bool) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldEnabled, v)) +} + +// PaymentMode applies equality check predicate on the "payment_mode" field. It's identical to PaymentModeEQ. +func PaymentMode(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldPaymentMode, v)) +} + +// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ. +func SortOrder(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSortOrder, v)) +} + +// Limits applies equality check predicate on the "limits" field. It's identical to LimitsEQ. +func Limits(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldLimits, v)) +} + +// RefundEnabled applies equality check predicate on the "refund_enabled" field. It's identical to RefundEnabledEQ. +func RefundEnabled(v bool) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldRefundEnabled, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(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.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// ProviderKeyEQ applies the EQ predicate on the "provider_key" field. +func ProviderKeyEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldProviderKey, v)) +} + +// ProviderKeyNEQ applies the NEQ predicate on the "provider_key" field. +func ProviderKeyNEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldProviderKey, v)) +} + +// ProviderKeyIn applies the In predicate on the "provider_key" field. +func ProviderKeyIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldProviderKey, vs...)) +} + +// ProviderKeyNotIn applies the NotIn predicate on the "provider_key" field. +func ProviderKeyNotIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldProviderKey, vs...)) +} + +// ProviderKeyGT applies the GT predicate on the "provider_key" field. +func ProviderKeyGT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldProviderKey, v)) +} + +// ProviderKeyGTE applies the GTE predicate on the "provider_key" field. +func ProviderKeyGTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldProviderKey, v)) +} + +// ProviderKeyLT applies the LT predicate on the "provider_key" field. +func ProviderKeyLT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldProviderKey, v)) +} + +// ProviderKeyLTE applies the LTE predicate on the "provider_key" field. +func ProviderKeyLTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldProviderKey, v)) +} + +// ProviderKeyContains applies the Contains predicate on the "provider_key" field. +func ProviderKeyContains(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContains(FieldProviderKey, v)) +} + +// ProviderKeyHasPrefix applies the HasPrefix predicate on the "provider_key" field. +func ProviderKeyHasPrefix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldProviderKey, v)) +} + +// ProviderKeyHasSuffix applies the HasSuffix predicate on the "provider_key" field. +func ProviderKeyHasSuffix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldProviderKey, v)) +} + +// ProviderKeyEqualFold applies the EqualFold predicate on the "provider_key" field. +func ProviderKeyEqualFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldProviderKey, v)) +} + +// ProviderKeyContainsFold applies the ContainsFold predicate on the "provider_key" field. +func ProviderKeyContainsFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldProviderKey, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldName, v)) +} + +// ConfigEQ applies the EQ predicate on the "config" field. +func ConfigEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldConfig, v)) +} + +// ConfigNEQ applies the NEQ predicate on the "config" field. +func ConfigNEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldConfig, v)) +} + +// ConfigIn applies the In predicate on the "config" field. +func ConfigIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldConfig, vs...)) +} + +// ConfigNotIn applies the NotIn predicate on the "config" field. +func ConfigNotIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldConfig, vs...)) +} + +// ConfigGT applies the GT predicate on the "config" field. +func ConfigGT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldConfig, v)) +} + +// ConfigGTE applies the GTE predicate on the "config" field. +func ConfigGTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldConfig, v)) +} + +// ConfigLT applies the LT predicate on the "config" field. +func ConfigLT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldConfig, v)) +} + +// ConfigLTE applies the LTE predicate on the "config" field. +func ConfigLTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldConfig, v)) +} + +// ConfigContains applies the Contains predicate on the "config" field. +func ConfigContains(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContains(FieldConfig, v)) +} + +// ConfigHasPrefix applies the HasPrefix predicate on the "config" field. +func ConfigHasPrefix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldConfig, v)) +} + +// ConfigHasSuffix applies the HasSuffix predicate on the "config" field. +func ConfigHasSuffix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldConfig, v)) +} + +// ConfigEqualFold applies the EqualFold predicate on the "config" field. +func ConfigEqualFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldConfig, v)) +} + +// ConfigContainsFold applies the ContainsFold predicate on the "config" field. +func ConfigContainsFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldConfig, v)) +} + +// SupportedTypesEQ applies the EQ predicate on the "supported_types" field. +func SupportedTypesEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSupportedTypes, v)) +} + +// SupportedTypesNEQ applies the NEQ predicate on the "supported_types" field. +func SupportedTypesNEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldSupportedTypes, v)) +} + +// SupportedTypesIn applies the In predicate on the "supported_types" field. +func SupportedTypesIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldSupportedTypes, vs...)) +} + +// SupportedTypesNotIn applies the NotIn predicate on the "supported_types" field. +func SupportedTypesNotIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldSupportedTypes, vs...)) +} + +// SupportedTypesGT applies the GT predicate on the "supported_types" field. +func SupportedTypesGT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldSupportedTypes, v)) +} + +// SupportedTypesGTE applies the GTE predicate on the "supported_types" field. +func SupportedTypesGTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldSupportedTypes, v)) +} + +// SupportedTypesLT applies the LT predicate on the "supported_types" field. +func SupportedTypesLT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldSupportedTypes, v)) +} + +// SupportedTypesLTE applies the LTE predicate on the "supported_types" field. +func SupportedTypesLTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldSupportedTypes, v)) +} + +// SupportedTypesContains applies the Contains predicate on the "supported_types" field. +func SupportedTypesContains(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContains(FieldSupportedTypes, v)) +} + +// SupportedTypesHasPrefix applies the HasPrefix predicate on the "supported_types" field. +func SupportedTypesHasPrefix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldSupportedTypes, v)) +} + +// SupportedTypesHasSuffix applies the HasSuffix predicate on the "supported_types" field. +func SupportedTypesHasSuffix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldSupportedTypes, v)) +} + +// SupportedTypesEqualFold applies the EqualFold predicate on the "supported_types" field. +func SupportedTypesEqualFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldSupportedTypes, v)) +} + +// SupportedTypesContainsFold applies the ContainsFold predicate on the "supported_types" field. +func SupportedTypesContainsFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldSupportedTypes, v)) +} + +// EnabledEQ applies the EQ predicate on the "enabled" field. +func EnabledEQ(v bool) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldEnabled, v)) +} + +// EnabledNEQ applies the NEQ predicate on the "enabled" field. +func EnabledNEQ(v bool) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldEnabled, v)) +} + +// PaymentModeEQ applies the EQ predicate on the "payment_mode" field. +func PaymentModeEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldPaymentMode, v)) +} + +// PaymentModeNEQ applies the NEQ predicate on the "payment_mode" field. +func PaymentModeNEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldPaymentMode, v)) +} + +// PaymentModeIn applies the In predicate on the "payment_mode" field. +func PaymentModeIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldPaymentMode, vs...)) +} + +// PaymentModeNotIn applies the NotIn predicate on the "payment_mode" field. +func PaymentModeNotIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldPaymentMode, vs...)) +} + +// PaymentModeGT applies the GT predicate on the "payment_mode" field. +func PaymentModeGT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldPaymentMode, v)) +} + +// PaymentModeGTE applies the GTE predicate on the "payment_mode" field. +func PaymentModeGTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldPaymentMode, v)) +} + +// PaymentModeLT applies the LT predicate on the "payment_mode" field. +func PaymentModeLT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldPaymentMode, v)) +} + +// PaymentModeLTE applies the LTE predicate on the "payment_mode" field. +func PaymentModeLTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldPaymentMode, v)) +} + +// PaymentModeContains applies the Contains predicate on the "payment_mode" field. +func PaymentModeContains(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContains(FieldPaymentMode, v)) +} + +// PaymentModeHasPrefix applies the HasPrefix predicate on the "payment_mode" field. +func PaymentModeHasPrefix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldPaymentMode, v)) +} + +// PaymentModeHasSuffix applies the HasSuffix predicate on the "payment_mode" field. +func PaymentModeHasSuffix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldPaymentMode, v)) +} + +// PaymentModeEqualFold applies the EqualFold predicate on the "payment_mode" field. +func PaymentModeEqualFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldPaymentMode, v)) +} + +// PaymentModeContainsFold applies the ContainsFold predicate on the "payment_mode" field. +func PaymentModeContainsFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldPaymentMode, v)) +} + +// SortOrderEQ applies the EQ predicate on the "sort_order" field. +func SortOrderEQ(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSortOrder, v)) +} + +// SortOrderNEQ applies the NEQ predicate on the "sort_order" field. +func SortOrderNEQ(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldSortOrder, v)) +} + +// SortOrderIn applies the In predicate on the "sort_order" field. +func SortOrderIn(vs ...int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldSortOrder, vs...)) +} + +// SortOrderNotIn applies the NotIn predicate on the "sort_order" field. +func SortOrderNotIn(vs ...int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldSortOrder, vs...)) +} + +// SortOrderGT applies the GT predicate on the "sort_order" field. +func SortOrderGT(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldSortOrder, v)) +} + +// SortOrderGTE applies the GTE predicate on the "sort_order" field. +func SortOrderGTE(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldSortOrder, v)) +} + +// SortOrderLT applies the LT predicate on the "sort_order" field. +func SortOrderLT(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldSortOrder, v)) +} + +// SortOrderLTE applies the LTE predicate on the "sort_order" field. +func SortOrderLTE(v int) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldSortOrder, v)) +} + +// LimitsEQ applies the EQ predicate on the "limits" field. +func LimitsEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldLimits, v)) +} + +// LimitsNEQ applies the NEQ predicate on the "limits" field. +func LimitsNEQ(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldLimits, v)) +} + +// LimitsIn applies the In predicate on the "limits" field. +func LimitsIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldLimits, vs...)) +} + +// LimitsNotIn applies the NotIn predicate on the "limits" field. +func LimitsNotIn(vs ...string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldLimits, vs...)) +} + +// LimitsGT applies the GT predicate on the "limits" field. +func LimitsGT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldLimits, v)) +} + +// LimitsGTE applies the GTE predicate on the "limits" field. +func LimitsGTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldLimits, v)) +} + +// LimitsLT applies the LT predicate on the "limits" field. +func LimitsLT(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldLimits, v)) +} + +// LimitsLTE applies the LTE predicate on the "limits" field. +func LimitsLTE(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldLimits, v)) +} + +// LimitsContains applies the Contains predicate on the "limits" field. +func LimitsContains(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContains(FieldLimits, v)) +} + +// LimitsHasPrefix applies the HasPrefix predicate on the "limits" field. +func LimitsHasPrefix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldLimits, v)) +} + +// LimitsHasSuffix applies the HasSuffix predicate on the "limits" field. +func LimitsHasSuffix(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldLimits, v)) +} + +// LimitsEqualFold applies the EqualFold predicate on the "limits" field. +func LimitsEqualFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldLimits, v)) +} + +// LimitsContainsFold applies the ContainsFold predicate on the "limits" field. +func LimitsContainsFold(v string) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldLimits, v)) +} + +// RefundEnabledEQ applies the EQ predicate on the "refund_enabled" field. +func RefundEnabledEQ(v bool) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldRefundEnabled, v)) +} + +// RefundEnabledNEQ applies the NEQ predicate on the "refund_enabled" field. +func RefundEnabledNEQ(v bool) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldRefundEnabled, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.PaymentProviderInstance) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.PaymentProviderInstance) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.PaymentProviderInstance) predicate.PaymentProviderInstance { + return predicate.PaymentProviderInstance(sql.NotPredicates(p)) +} diff --git a/backend/ent/paymentproviderinstance_create.go b/backend/ent/paymentproviderinstance_create.go new file mode 100644 index 0000000000000000000000000000000000000000..20b16ddd42e6aaee245920b6dc4bc44858fc66a4 --- /dev/null +++ b/backend/ent/paymentproviderinstance_create.go @@ -0,0 +1,1111 @@ +// 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/paymentproviderinstance" +) + +// PaymentProviderInstanceCreate is the builder for creating a PaymentProviderInstance entity. +type PaymentProviderInstanceCreate struct { + config + mutation *PaymentProviderInstanceMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetProviderKey sets the "provider_key" field. +func (_c *PaymentProviderInstanceCreate) SetProviderKey(v string) *PaymentProviderInstanceCreate { + _c.mutation.SetProviderKey(v) + return _c +} + +// SetName sets the "name" field. +func (_c *PaymentProviderInstanceCreate) SetName(v string) *PaymentProviderInstanceCreate { + _c.mutation.SetName(v) + return _c +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableName(v *string) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetName(*v) + } + return _c +} + +// SetConfig sets the "config" field. +func (_c *PaymentProviderInstanceCreate) SetConfig(v string) *PaymentProviderInstanceCreate { + _c.mutation.SetConfig(v) + return _c +} + +// SetSupportedTypes sets the "supported_types" field. +func (_c *PaymentProviderInstanceCreate) SetSupportedTypes(v string) *PaymentProviderInstanceCreate { + _c.mutation.SetSupportedTypes(v) + return _c +} + +// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetSupportedTypes(*v) + } + return _c +} + +// SetEnabled sets the "enabled" field. +func (_c *PaymentProviderInstanceCreate) SetEnabled(v bool) *PaymentProviderInstanceCreate { + _c.mutation.SetEnabled(v) + return _c +} + +// SetNillableEnabled sets the "enabled" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableEnabled(v *bool) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetEnabled(*v) + } + return _c +} + +// SetPaymentMode sets the "payment_mode" field. +func (_c *PaymentProviderInstanceCreate) SetPaymentMode(v string) *PaymentProviderInstanceCreate { + _c.mutation.SetPaymentMode(v) + return _c +} + +// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillablePaymentMode(v *string) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetPaymentMode(*v) + } + return _c +} + +// SetSortOrder sets the "sort_order" field. +func (_c *PaymentProviderInstanceCreate) SetSortOrder(v int) *PaymentProviderInstanceCreate { + _c.mutation.SetSortOrder(v) + return _c +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableSortOrder(v *int) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetSortOrder(*v) + } + return _c +} + +// SetLimits sets the "limits" field. +func (_c *PaymentProviderInstanceCreate) SetLimits(v string) *PaymentProviderInstanceCreate { + _c.mutation.SetLimits(v) + return _c +} + +// SetNillableLimits sets the "limits" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableLimits(v *string) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetLimits(*v) + } + return _c +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (_c *PaymentProviderInstanceCreate) SetRefundEnabled(v bool) *PaymentProviderInstanceCreate { + _c.mutation.SetRefundEnabled(v) + return _c +} + +// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetRefundEnabled(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *PaymentProviderInstanceCreate) SetCreatedAt(v time.Time) *PaymentProviderInstanceCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableCreatedAt(v *time.Time) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *PaymentProviderInstanceCreate) SetUpdatedAt(v time.Time) *PaymentProviderInstanceCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *PaymentProviderInstanceCreate) SetNillableUpdatedAt(v *time.Time) *PaymentProviderInstanceCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// Mutation returns the PaymentProviderInstanceMutation object of the builder. +func (_c *PaymentProviderInstanceCreate) Mutation() *PaymentProviderInstanceMutation { + return _c.mutation +} + +// Save creates the PaymentProviderInstance in the database. +func (_c *PaymentProviderInstanceCreate) Save(ctx context.Context) (*PaymentProviderInstance, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *PaymentProviderInstanceCreate) SaveX(ctx context.Context) *PaymentProviderInstance { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *PaymentProviderInstanceCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *PaymentProviderInstanceCreate) 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 *PaymentProviderInstanceCreate) defaults() { + if _, ok := _c.mutation.Name(); !ok { + v := paymentproviderinstance.DefaultName + _c.mutation.SetName(v) + } + if _, ok := _c.mutation.SupportedTypes(); !ok { + v := paymentproviderinstance.DefaultSupportedTypes + _c.mutation.SetSupportedTypes(v) + } + if _, ok := _c.mutation.Enabled(); !ok { + v := paymentproviderinstance.DefaultEnabled + _c.mutation.SetEnabled(v) + } + if _, ok := _c.mutation.PaymentMode(); !ok { + v := paymentproviderinstance.DefaultPaymentMode + _c.mutation.SetPaymentMode(v) + } + if _, ok := _c.mutation.SortOrder(); !ok { + v := paymentproviderinstance.DefaultSortOrder + _c.mutation.SetSortOrder(v) + } + if _, ok := _c.mutation.Limits(); !ok { + v := paymentproviderinstance.DefaultLimits + _c.mutation.SetLimits(v) + } + if _, ok := _c.mutation.RefundEnabled(); !ok { + v := paymentproviderinstance.DefaultRefundEnabled + _c.mutation.SetRefundEnabled(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := paymentproviderinstance.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := paymentproviderinstance.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *PaymentProviderInstanceCreate) check() error { + if _, ok := _c.mutation.ProviderKey(); !ok { + return &ValidationError{Name: "provider_key", err: errors.New(`ent: missing required field "PaymentProviderInstance.provider_key"`)} + } + if v, ok := _c.mutation.ProviderKey(); ok { + if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil { + return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)} + } + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "PaymentProviderInstance.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := paymentproviderinstance.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)} + } + } + if _, ok := _c.mutation.Config(); !ok { + return &ValidationError{Name: "config", err: errors.New(`ent: missing required field "PaymentProviderInstance.config"`)} + } + if _, ok := _c.mutation.SupportedTypes(); !ok { + return &ValidationError{Name: "supported_types", err: errors.New(`ent: missing required field "PaymentProviderInstance.supported_types"`)} + } + if v, ok := _c.mutation.SupportedTypes(); ok { + if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil { + return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)} + } + } + if _, ok := _c.mutation.Enabled(); !ok { + return &ValidationError{Name: "enabled", err: errors.New(`ent: missing required field "PaymentProviderInstance.enabled"`)} + } + if _, ok := _c.mutation.PaymentMode(); !ok { + return &ValidationError{Name: "payment_mode", err: errors.New(`ent: missing required field "PaymentProviderInstance.payment_mode"`)} + } + if v, ok := _c.mutation.PaymentMode(); ok { + if err := paymentproviderinstance.PaymentModeValidator(v); err != nil { + return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)} + } + } + if _, ok := _c.mutation.SortOrder(); !ok { + return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "PaymentProviderInstance.sort_order"`)} + } + if _, ok := _c.mutation.Limits(); !ok { + return &ValidationError{Name: "limits", err: errors.New(`ent: missing required field "PaymentProviderInstance.limits"`)} + } + if _, ok := _c.mutation.RefundEnabled(); !ok { + return &ValidationError{Name: "refund_enabled", err: errors.New(`ent: missing required field "PaymentProviderInstance.refund_enabled"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentProviderInstance.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "PaymentProviderInstance.updated_at"`)} + } + return nil +} + +func (_c *PaymentProviderInstanceCreate) sqlSave(ctx context.Context) (*PaymentProviderInstance, 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 *PaymentProviderInstanceCreate) createSpec() (*PaymentProviderInstance, *sqlgraph.CreateSpec) { + var ( + _node = &PaymentProviderInstance{config: _c.config} + _spec = sqlgraph.NewCreateSpec(paymentproviderinstance.Table, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if value, ok := _c.mutation.ProviderKey(); ok { + _spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value) + _node.ProviderKey = value + } + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Config(); ok { + _spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value) + _node.Config = value + } + if value, ok := _c.mutation.SupportedTypes(); ok { + _spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value) + _node.SupportedTypes = value + } + if value, ok := _c.mutation.Enabled(); ok { + _spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value) + _node.Enabled = value + } + if value, ok := _c.mutation.PaymentMode(); ok { + _spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value) + _node.PaymentMode = value + } + if value, ok := _c.mutation.SortOrder(); ok { + _spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value) + _node.SortOrder = value + } + if value, ok := _c.mutation.Limits(); ok { + _spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value) + _node.Limits = value + } + if value, ok := _c.mutation.RefundEnabled(); ok { + _spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value) + _node.RefundEnabled = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(paymentproviderinstance.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.PaymentProviderInstance.Create(). +// SetProviderKey(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.PaymentProviderInstanceUpsert) { +// SetProviderKey(v+v). +// }). +// Exec(ctx) +func (_c *PaymentProviderInstanceCreate) OnConflict(opts ...sql.ConflictOption) *PaymentProviderInstanceUpsertOne { + _c.conflict = opts + return &PaymentProviderInstanceUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.PaymentProviderInstance.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *PaymentProviderInstanceCreate) OnConflictColumns(columns ...string) *PaymentProviderInstanceUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &PaymentProviderInstanceUpsertOne{ + create: _c, + } +} + +type ( + // PaymentProviderInstanceUpsertOne is the builder for "upsert"-ing + // one PaymentProviderInstance node. + PaymentProviderInstanceUpsertOne struct { + create *PaymentProviderInstanceCreate + } + + // PaymentProviderInstanceUpsert is the "OnConflict" setter. + PaymentProviderInstanceUpsert struct { + *sql.UpdateSet + } +) + +// SetProviderKey sets the "provider_key" field. +func (u *PaymentProviderInstanceUpsert) SetProviderKey(v string) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldProviderKey, v) + return u +} + +// UpdateProviderKey sets the "provider_key" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateProviderKey() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldProviderKey) + return u +} + +// SetName sets the "name" field. +func (u *PaymentProviderInstanceUpsert) SetName(v string) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldName, v) + return u +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateName() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldName) + return u +} + +// SetConfig sets the "config" field. +func (u *PaymentProviderInstanceUpsert) SetConfig(v string) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldConfig, v) + return u +} + +// UpdateConfig sets the "config" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateConfig() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldConfig) + return u +} + +// SetSupportedTypes sets the "supported_types" field. +func (u *PaymentProviderInstanceUpsert) SetSupportedTypes(v string) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldSupportedTypes, v) + return u +} + +// UpdateSupportedTypes sets the "supported_types" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateSupportedTypes() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldSupportedTypes) + return u +} + +// SetEnabled sets the "enabled" field. +func (u *PaymentProviderInstanceUpsert) SetEnabled(v bool) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldEnabled, v) + return u +} + +// UpdateEnabled sets the "enabled" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateEnabled() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldEnabled) + return u +} + +// SetPaymentMode sets the "payment_mode" field. +func (u *PaymentProviderInstanceUpsert) SetPaymentMode(v string) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldPaymentMode, v) + return u +} + +// UpdatePaymentMode sets the "payment_mode" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdatePaymentMode() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldPaymentMode) + return u +} + +// SetSortOrder sets the "sort_order" field. +func (u *PaymentProviderInstanceUpsert) SetSortOrder(v int) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldSortOrder, v) + return u +} + +// UpdateSortOrder sets the "sort_order" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateSortOrder() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldSortOrder) + return u +} + +// AddSortOrder adds v to the "sort_order" field. +func (u *PaymentProviderInstanceUpsert) AddSortOrder(v int) *PaymentProviderInstanceUpsert { + u.Add(paymentproviderinstance.FieldSortOrder, v) + return u +} + +// SetLimits sets the "limits" field. +func (u *PaymentProviderInstanceUpsert) SetLimits(v string) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldLimits, v) + return u +} + +// UpdateLimits sets the "limits" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateLimits() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldLimits) + return u +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (u *PaymentProviderInstanceUpsert) SetRefundEnabled(v bool) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldRefundEnabled, v) + return u +} + +// UpdateRefundEnabled sets the "refund_enabled" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateRefundEnabled() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.FieldRefundEnabled) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *PaymentProviderInstanceUpsert) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpsert { + u.Set(paymentproviderinstance.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsert) UpdateUpdatedAt() *PaymentProviderInstanceUpsert { + u.SetExcluded(paymentproviderinstance.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.PaymentProviderInstance.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *PaymentProviderInstanceUpsertOne) UpdateNewValues() *PaymentProviderInstanceUpsertOne { + 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(paymentproviderinstance.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.PaymentProviderInstance.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *PaymentProviderInstanceUpsertOne) Ignore() *PaymentProviderInstanceUpsertOne { + 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 *PaymentProviderInstanceUpsertOne) DoNothing() *PaymentProviderInstanceUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the PaymentProviderInstanceCreate.OnConflict +// documentation for more info. +func (u *PaymentProviderInstanceUpsertOne) Update(set func(*PaymentProviderInstanceUpsert)) *PaymentProviderInstanceUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&PaymentProviderInstanceUpsert{UpdateSet: update}) + })) + return u +} + +// SetProviderKey sets the "provider_key" field. +func (u *PaymentProviderInstanceUpsertOne) SetProviderKey(v string) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetProviderKey(v) + }) +} + +// UpdateProviderKey sets the "provider_key" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateProviderKey() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateProviderKey() + }) +} + +// SetName sets the "name" field. +func (u *PaymentProviderInstanceUpsertOne) SetName(v string) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateName() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateName() + }) +} + +// SetConfig sets the "config" field. +func (u *PaymentProviderInstanceUpsertOne) SetConfig(v string) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetConfig(v) + }) +} + +// UpdateConfig sets the "config" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateConfig() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateConfig() + }) +} + +// SetSupportedTypes sets the "supported_types" field. +func (u *PaymentProviderInstanceUpsertOne) SetSupportedTypes(v string) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetSupportedTypes(v) + }) +} + +// UpdateSupportedTypes sets the "supported_types" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateSupportedTypes() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateSupportedTypes() + }) +} + +// SetEnabled sets the "enabled" field. +func (u *PaymentProviderInstanceUpsertOne) SetEnabled(v bool) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetEnabled(v) + }) +} + +// UpdateEnabled sets the "enabled" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateEnabled() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateEnabled() + }) +} + +// SetPaymentMode sets the "payment_mode" field. +func (u *PaymentProviderInstanceUpsertOne) SetPaymentMode(v string) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetPaymentMode(v) + }) +} + +// UpdatePaymentMode sets the "payment_mode" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdatePaymentMode() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdatePaymentMode() + }) +} + +// SetSortOrder sets the "sort_order" field. +func (u *PaymentProviderInstanceUpsertOne) SetSortOrder(v int) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetSortOrder(v) + }) +} + +// AddSortOrder adds v to the "sort_order" field. +func (u *PaymentProviderInstanceUpsertOne) AddSortOrder(v int) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.AddSortOrder(v) + }) +} + +// UpdateSortOrder sets the "sort_order" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateSortOrder() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateSortOrder() + }) +} + +// SetLimits sets the "limits" field. +func (u *PaymentProviderInstanceUpsertOne) SetLimits(v string) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetLimits(v) + }) +} + +// UpdateLimits sets the "limits" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateLimits() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateLimits() + }) +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (u *PaymentProviderInstanceUpsertOne) SetRefundEnabled(v bool) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetRefundEnabled(v) + }) +} + +// UpdateRefundEnabled sets the "refund_enabled" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateRefundEnabled() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateRefundEnabled() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *PaymentProviderInstanceUpsertOne) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertOne) UpdateUpdatedAt() *PaymentProviderInstanceUpsertOne { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *PaymentProviderInstanceUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for PaymentProviderInstanceCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *PaymentProviderInstanceUpsertOne) 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 *PaymentProviderInstanceUpsertOne) 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 *PaymentProviderInstanceUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// PaymentProviderInstanceCreateBulk is the builder for creating many PaymentProviderInstance entities in bulk. +type PaymentProviderInstanceCreateBulk struct { + config + err error + builders []*PaymentProviderInstanceCreate + conflict []sql.ConflictOption +} + +// Save creates the PaymentProviderInstance entities in the database. +func (_c *PaymentProviderInstanceCreateBulk) Save(ctx context.Context) ([]*PaymentProviderInstance, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*PaymentProviderInstance, 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.(*PaymentProviderInstanceMutation) + 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 *PaymentProviderInstanceCreateBulk) SaveX(ctx context.Context) []*PaymentProviderInstance { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *PaymentProviderInstanceCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *PaymentProviderInstanceCreateBulk) 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.PaymentProviderInstance.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.PaymentProviderInstanceUpsert) { +// SetProviderKey(v+v). +// }). +// Exec(ctx) +func (_c *PaymentProviderInstanceCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentProviderInstanceUpsertBulk { + _c.conflict = opts + return &PaymentProviderInstanceUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.PaymentProviderInstance.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *PaymentProviderInstanceCreateBulk) OnConflictColumns(columns ...string) *PaymentProviderInstanceUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &PaymentProviderInstanceUpsertBulk{ + create: _c, + } +} + +// PaymentProviderInstanceUpsertBulk is the builder for "upsert"-ing +// a bulk of PaymentProviderInstance nodes. +type PaymentProviderInstanceUpsertBulk struct { + create *PaymentProviderInstanceCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.PaymentProviderInstance.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *PaymentProviderInstanceUpsertBulk) UpdateNewValues() *PaymentProviderInstanceUpsertBulk { + 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(paymentproviderinstance.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.PaymentProviderInstance.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *PaymentProviderInstanceUpsertBulk) Ignore() *PaymentProviderInstanceUpsertBulk { + 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 *PaymentProviderInstanceUpsertBulk) DoNothing() *PaymentProviderInstanceUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the PaymentProviderInstanceCreateBulk.OnConflict +// documentation for more info. +func (u *PaymentProviderInstanceUpsertBulk) Update(set func(*PaymentProviderInstanceUpsert)) *PaymentProviderInstanceUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&PaymentProviderInstanceUpsert{UpdateSet: update}) + })) + return u +} + +// SetProviderKey sets the "provider_key" field. +func (u *PaymentProviderInstanceUpsertBulk) SetProviderKey(v string) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetProviderKey(v) + }) +} + +// UpdateProviderKey sets the "provider_key" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateProviderKey() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateProviderKey() + }) +} + +// SetName sets the "name" field. +func (u *PaymentProviderInstanceUpsertBulk) SetName(v string) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateName() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateName() + }) +} + +// SetConfig sets the "config" field. +func (u *PaymentProviderInstanceUpsertBulk) SetConfig(v string) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetConfig(v) + }) +} + +// UpdateConfig sets the "config" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateConfig() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateConfig() + }) +} + +// SetSupportedTypes sets the "supported_types" field. +func (u *PaymentProviderInstanceUpsertBulk) SetSupportedTypes(v string) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetSupportedTypes(v) + }) +} + +// UpdateSupportedTypes sets the "supported_types" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateSupportedTypes() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateSupportedTypes() + }) +} + +// SetEnabled sets the "enabled" field. +func (u *PaymentProviderInstanceUpsertBulk) SetEnabled(v bool) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetEnabled(v) + }) +} + +// UpdateEnabled sets the "enabled" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateEnabled() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateEnabled() + }) +} + +// SetPaymentMode sets the "payment_mode" field. +func (u *PaymentProviderInstanceUpsertBulk) SetPaymentMode(v string) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetPaymentMode(v) + }) +} + +// UpdatePaymentMode sets the "payment_mode" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdatePaymentMode() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdatePaymentMode() + }) +} + +// SetSortOrder sets the "sort_order" field. +func (u *PaymentProviderInstanceUpsertBulk) SetSortOrder(v int) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetSortOrder(v) + }) +} + +// AddSortOrder adds v to the "sort_order" field. +func (u *PaymentProviderInstanceUpsertBulk) AddSortOrder(v int) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.AddSortOrder(v) + }) +} + +// UpdateSortOrder sets the "sort_order" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateSortOrder() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateSortOrder() + }) +} + +// SetLimits sets the "limits" field. +func (u *PaymentProviderInstanceUpsertBulk) SetLimits(v string) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetLimits(v) + }) +} + +// UpdateLimits sets the "limits" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateLimits() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateLimits() + }) +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (u *PaymentProviderInstanceUpsertBulk) SetRefundEnabled(v bool) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetRefundEnabled(v) + }) +} + +// UpdateRefundEnabled sets the "refund_enabled" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateRefundEnabled() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateRefundEnabled() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *PaymentProviderInstanceUpsertBulk) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *PaymentProviderInstanceUpsertBulk) UpdateUpdatedAt() *PaymentProviderInstanceUpsertBulk { + return u.Update(func(s *PaymentProviderInstanceUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *PaymentProviderInstanceUpsertBulk) 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 PaymentProviderInstanceCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for PaymentProviderInstanceCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *PaymentProviderInstanceUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/paymentproviderinstance_delete.go b/backend/ent/paymentproviderinstance_delete.go new file mode 100644 index 0000000000000000000000000000000000000000..0cffe7310433c529aaf4e59d478e371eb61b613c --- /dev/null +++ b/backend/ent/paymentproviderinstance_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/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentProviderInstanceDelete is the builder for deleting a PaymentProviderInstance entity. +type PaymentProviderInstanceDelete struct { + config + hooks []Hook + mutation *PaymentProviderInstanceMutation +} + +// Where appends a list predicates to the PaymentProviderInstanceDelete builder. +func (_d *PaymentProviderInstanceDelete) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *PaymentProviderInstanceDelete) 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 *PaymentProviderInstanceDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *PaymentProviderInstanceDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(paymentproviderinstance.Table, sqlgraph.NewFieldSpec(paymentproviderinstance.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 +} + +// PaymentProviderInstanceDeleteOne is the builder for deleting a single PaymentProviderInstance entity. +type PaymentProviderInstanceDeleteOne struct { + _d *PaymentProviderInstanceDelete +} + +// Where appends a list predicates to the PaymentProviderInstanceDelete builder. +func (_d *PaymentProviderInstanceDeleteOne) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *PaymentProviderInstanceDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{paymentproviderinstance.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *PaymentProviderInstanceDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/paymentproviderinstance_query.go b/backend/ent/paymentproviderinstance_query.go new file mode 100644 index 0000000000000000000000000000000000000000..c021208858c7f681ea0449652cd9341cdba7b478 --- /dev/null +++ b/backend/ent/paymentproviderinstance_query.go @@ -0,0 +1,564 @@ +// 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/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentProviderInstanceQuery is the builder for querying PaymentProviderInstance entities. +type PaymentProviderInstanceQuery struct { + config + ctx *QueryContext + order []paymentproviderinstance.OrderOption + inters []Interceptor + predicates []predicate.PaymentProviderInstance + 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 PaymentProviderInstanceQuery builder. +func (_q *PaymentProviderInstanceQuery) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *PaymentProviderInstanceQuery) Limit(limit int) *PaymentProviderInstanceQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *PaymentProviderInstanceQuery) Offset(offset int) *PaymentProviderInstanceQuery { + _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 *PaymentProviderInstanceQuery) Unique(unique bool) *PaymentProviderInstanceQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *PaymentProviderInstanceQuery) Order(o ...paymentproviderinstance.OrderOption) *PaymentProviderInstanceQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first PaymentProviderInstance entity from the query. +// Returns a *NotFoundError when no PaymentProviderInstance was found. +func (_q *PaymentProviderInstanceQuery) First(ctx context.Context) (*PaymentProviderInstance, 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{paymentproviderinstance.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) FirstX(ctx context.Context) *PaymentProviderInstance { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first PaymentProviderInstance ID from the query. +// Returns a *NotFoundError when no PaymentProviderInstance ID was found. +func (_q *PaymentProviderInstanceQuery) 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{paymentproviderinstance.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single PaymentProviderInstance entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one PaymentProviderInstance entity is found. +// Returns a *NotFoundError when no PaymentProviderInstance entities are found. +func (_q *PaymentProviderInstanceQuery) Only(ctx context.Context) (*PaymentProviderInstance, 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{paymentproviderinstance.Label} + default: + return nil, &NotSingularError{paymentproviderinstance.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) OnlyX(ctx context.Context) *PaymentProviderInstance { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only PaymentProviderInstance ID in the query. +// Returns a *NotSingularError when more than one PaymentProviderInstance ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *PaymentProviderInstanceQuery) 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{paymentproviderinstance.Label} + default: + err = &NotSingularError{paymentproviderinstance.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) 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 PaymentProviderInstances. +func (_q *PaymentProviderInstanceQuery) All(ctx context.Context) ([]*PaymentProviderInstance, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*PaymentProviderInstance, *PaymentProviderInstanceQuery]() + return withInterceptors[[]*PaymentProviderInstance](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) AllX(ctx context.Context) []*PaymentProviderInstance { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of PaymentProviderInstance IDs. +func (_q *PaymentProviderInstanceQuery) 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(paymentproviderinstance.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) 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[*PaymentProviderInstanceQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the PaymentProviderInstanceQuery 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 *PaymentProviderInstanceQuery) Clone() *PaymentProviderInstanceQuery { + if _q == nil { + return nil + } + return &PaymentProviderInstanceQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]paymentproviderinstance.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.PaymentProviderInstance{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// 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 { +// ProviderKey string `json:"provider_key,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.PaymentProviderInstance.Query(). +// GroupBy(paymentproviderinstance.FieldProviderKey). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *PaymentProviderInstanceQuery) GroupBy(field string, fields ...string) *PaymentProviderInstanceGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &PaymentProviderInstanceGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = paymentproviderinstance.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 { +// ProviderKey string `json:"provider_key,omitempty"` +// } +// +// client.PaymentProviderInstance.Query(). +// Select(paymentproviderinstance.FieldProviderKey). +// Scan(ctx, &v) +func (_q *PaymentProviderInstanceQuery) Select(fields ...string) *PaymentProviderInstanceSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &PaymentProviderInstanceSelect{PaymentProviderInstanceQuery: _q} + sbuild.label = paymentproviderinstance.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a PaymentProviderInstanceSelect configured with the given aggregations. +func (_q *PaymentProviderInstanceQuery) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *PaymentProviderInstanceQuery) 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 !paymentproviderinstance.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 *PaymentProviderInstanceQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentProviderInstance, error) { + var ( + nodes = []*PaymentProviderInstance{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*PaymentProviderInstance).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &PaymentProviderInstance{config: _q.config} + 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) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *PaymentProviderInstanceQuery) 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 *PaymentProviderInstanceQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.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, paymentproviderinstance.FieldID) + for i := range fields { + if fields[i] != paymentproviderinstance.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 *PaymentProviderInstanceQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(paymentproviderinstance.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = paymentproviderinstance.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 *PaymentProviderInstanceQuery) ForUpdate(opts ...sql.LockOption) *PaymentProviderInstanceQuery { + 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 *PaymentProviderInstanceQuery) ForShare(opts ...sql.LockOption) *PaymentProviderInstanceQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// PaymentProviderInstanceGroupBy is the group-by builder for PaymentProviderInstance entities. +type PaymentProviderInstanceGroupBy struct { + selector + build *PaymentProviderInstanceQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *PaymentProviderInstanceGroupBy) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *PaymentProviderInstanceGroupBy) 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[*PaymentProviderInstanceQuery, *PaymentProviderInstanceGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *PaymentProviderInstanceGroupBy) sqlScan(ctx context.Context, root *PaymentProviderInstanceQuery, 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) +} + +// PaymentProviderInstanceSelect is the builder for selecting fields of PaymentProviderInstance entities. +type PaymentProviderInstanceSelect struct { + *PaymentProviderInstanceQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *PaymentProviderInstanceSelect) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *PaymentProviderInstanceSelect) 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[*PaymentProviderInstanceQuery, *PaymentProviderInstanceSelect](ctx, _s.PaymentProviderInstanceQuery, _s, _s.inters, v) +} + +func (_s *PaymentProviderInstanceSelect) sqlScan(ctx context.Context, root *PaymentProviderInstanceQuery, 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/paymentproviderinstance_update.go b/backend/ent/paymentproviderinstance_update.go new file mode 100644 index 0000000000000000000000000000000000000000..06dba52740c2e425b0a4b615b2f00e1baacb128d --- /dev/null +++ b/backend/ent/paymentproviderinstance_update.go @@ -0,0 +1,594 @@ +// 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/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// PaymentProviderInstanceUpdate is the builder for updating PaymentProviderInstance entities. +type PaymentProviderInstanceUpdate struct { + config + hooks []Hook + mutation *PaymentProviderInstanceMutation +} + +// Where appends a list predicates to the PaymentProviderInstanceUpdate builder. +func (_u *PaymentProviderInstanceUpdate) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetProviderKey sets the "provider_key" field. +func (_u *PaymentProviderInstanceUpdate) SetProviderKey(v string) *PaymentProviderInstanceUpdate { + _u.mutation.SetProviderKey(v) + return _u +} + +// SetNillableProviderKey sets the "provider_key" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableProviderKey(v *string) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetProviderKey(*v) + } + return _u +} + +// SetName sets the "name" field. +func (_u *PaymentProviderInstanceUpdate) SetName(v string) *PaymentProviderInstanceUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableName(v *string) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetConfig sets the "config" field. +func (_u *PaymentProviderInstanceUpdate) SetConfig(v string) *PaymentProviderInstanceUpdate { + _u.mutation.SetConfig(v) + return _u +} + +// SetNillableConfig sets the "config" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableConfig(v *string) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetConfig(*v) + } + return _u +} + +// SetSupportedTypes sets the "supported_types" field. +func (_u *PaymentProviderInstanceUpdate) SetSupportedTypes(v string) *PaymentProviderInstanceUpdate { + _u.mutation.SetSupportedTypes(v) + return _u +} + +// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetSupportedTypes(*v) + } + return _u +} + +// SetEnabled sets the "enabled" field. +func (_u *PaymentProviderInstanceUpdate) SetEnabled(v bool) *PaymentProviderInstanceUpdate { + _u.mutation.SetEnabled(v) + return _u +} + +// SetNillableEnabled sets the "enabled" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableEnabled(v *bool) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetEnabled(*v) + } + return _u +} + +// SetPaymentMode sets the "payment_mode" field. +func (_u *PaymentProviderInstanceUpdate) SetPaymentMode(v string) *PaymentProviderInstanceUpdate { + _u.mutation.SetPaymentMode(v) + return _u +} + +// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillablePaymentMode(v *string) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetPaymentMode(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *PaymentProviderInstanceUpdate) SetSortOrder(v int) *PaymentProviderInstanceUpdate { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableSortOrder(v *int) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *PaymentProviderInstanceUpdate) AddSortOrder(v int) *PaymentProviderInstanceUpdate { + _u.mutation.AddSortOrder(v) + return _u +} + +// SetLimits sets the "limits" field. +func (_u *PaymentProviderInstanceUpdate) SetLimits(v string) *PaymentProviderInstanceUpdate { + _u.mutation.SetLimits(v) + return _u +} + +// SetNillableLimits sets the "limits" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableLimits(v *string) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetLimits(*v) + } + return _u +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (_u *PaymentProviderInstanceUpdate) SetRefundEnabled(v bool) *PaymentProviderInstanceUpdate { + _u.mutation.SetRefundEnabled(v) + return _u +} + +// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdate) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceUpdate { + if v != nil { + _u.SetRefundEnabled(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *PaymentProviderInstanceUpdate) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the PaymentProviderInstanceMutation object of the builder. +func (_u *PaymentProviderInstanceUpdate) Mutation() *PaymentProviderInstanceMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *PaymentProviderInstanceUpdate) 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 *PaymentProviderInstanceUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *PaymentProviderInstanceUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *PaymentProviderInstanceUpdate) 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 *PaymentProviderInstanceUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := paymentproviderinstance.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *PaymentProviderInstanceUpdate) check() error { + if v, ok := _u.mutation.ProviderKey(); ok { + if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil { + return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)} + } + } + if v, ok := _u.mutation.Name(); ok { + if err := paymentproviderinstance.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)} + } + } + if v, ok := _u.mutation.SupportedTypes(); ok { + if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil { + return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)} + } + } + if v, ok := _u.mutation.PaymentMode(); ok { + if err := paymentproviderinstance.PaymentModeValidator(v); err != nil { + return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)} + } + } + return nil +} + +func (_u *PaymentProviderInstanceUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.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.ProviderKey(); ok { + _spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value) + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Config(); ok { + _spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value) + } + if value, ok := _u.mutation.SupportedTypes(); ok { + _spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value) + } + if value, ok := _u.mutation.Enabled(); ok { + _spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value) + } + if value, ok := _u.mutation.PaymentMode(); ok { + _spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.Limits(); ok { + _spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value) + } + if value, ok := _u.mutation.RefundEnabled(); ok { + _spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{paymentproviderinstance.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// PaymentProviderInstanceUpdateOne is the builder for updating a single PaymentProviderInstance entity. +type PaymentProviderInstanceUpdateOne struct { + config + fields []string + hooks []Hook + mutation *PaymentProviderInstanceMutation +} + +// SetProviderKey sets the "provider_key" field. +func (_u *PaymentProviderInstanceUpdateOne) SetProviderKey(v string) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetProviderKey(v) + return _u +} + +// SetNillableProviderKey sets the "provider_key" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableProviderKey(v *string) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetProviderKey(*v) + } + return _u +} + +// SetName sets the "name" field. +func (_u *PaymentProviderInstanceUpdateOne) SetName(v string) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableName(v *string) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetConfig sets the "config" field. +func (_u *PaymentProviderInstanceUpdateOne) SetConfig(v string) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetConfig(v) + return _u +} + +// SetNillableConfig sets the "config" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableConfig(v *string) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetConfig(*v) + } + return _u +} + +// SetSupportedTypes sets the "supported_types" field. +func (_u *PaymentProviderInstanceUpdateOne) SetSupportedTypes(v string) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetSupportedTypes(v) + return _u +} + +// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetSupportedTypes(*v) + } + return _u +} + +// SetEnabled sets the "enabled" field. +func (_u *PaymentProviderInstanceUpdateOne) SetEnabled(v bool) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetEnabled(v) + return _u +} + +// SetNillableEnabled sets the "enabled" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableEnabled(v *bool) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetEnabled(*v) + } + return _u +} + +// SetPaymentMode sets the "payment_mode" field. +func (_u *PaymentProviderInstanceUpdateOne) SetPaymentMode(v string) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetPaymentMode(v) + return _u +} + +// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillablePaymentMode(v *string) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetPaymentMode(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *PaymentProviderInstanceUpdateOne) SetSortOrder(v int) *PaymentProviderInstanceUpdateOne { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableSortOrder(v *int) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *PaymentProviderInstanceUpdateOne) AddSortOrder(v int) *PaymentProviderInstanceUpdateOne { + _u.mutation.AddSortOrder(v) + return _u +} + +// SetLimits sets the "limits" field. +func (_u *PaymentProviderInstanceUpdateOne) SetLimits(v string) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetLimits(v) + return _u +} + +// SetNillableLimits sets the "limits" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableLimits(v *string) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetLimits(*v) + } + return _u +} + +// SetRefundEnabled sets the "refund_enabled" field. +func (_u *PaymentProviderInstanceUpdateOne) SetRefundEnabled(v bool) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetRefundEnabled(v) + return _u +} + +// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil. +func (_u *PaymentProviderInstanceUpdateOne) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceUpdateOne { + if v != nil { + _u.SetRefundEnabled(*v) + } + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *PaymentProviderInstanceUpdateOne) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the PaymentProviderInstanceMutation object of the builder. +func (_u *PaymentProviderInstanceUpdateOne) Mutation() *PaymentProviderInstanceMutation { + return _u.mutation +} + +// Where appends a list predicates to the PaymentProviderInstanceUpdate builder. +func (_u *PaymentProviderInstanceUpdateOne) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceUpdateOne { + _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 *PaymentProviderInstanceUpdateOne) Select(field string, fields ...string) *PaymentProviderInstanceUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated PaymentProviderInstance entity. +func (_u *PaymentProviderInstanceUpdateOne) Save(ctx context.Context) (*PaymentProviderInstance, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *PaymentProviderInstanceUpdateOne) SaveX(ctx context.Context) *PaymentProviderInstance { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *PaymentProviderInstanceUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *PaymentProviderInstanceUpdateOne) 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 *PaymentProviderInstanceUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := paymentproviderinstance.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *PaymentProviderInstanceUpdateOne) check() error { + if v, ok := _u.mutation.ProviderKey(); ok { + if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil { + return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)} + } + } + if v, ok := _u.mutation.Name(); ok { + if err := paymentproviderinstance.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)} + } + } + if v, ok := _u.mutation.SupportedTypes(); ok { + if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil { + return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)} + } + } + if v, ok := _u.mutation.PaymentMode(); ok { + if err := paymentproviderinstance.PaymentModeValidator(v); err != nil { + return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)} + } + } + return nil +} + +func (_u *PaymentProviderInstanceUpdateOne) sqlSave(ctx context.Context) (_node *PaymentProviderInstance, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentProviderInstance.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, paymentproviderinstance.FieldID) + for _, f := range fields { + if !paymentproviderinstance.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != paymentproviderinstance.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.ProviderKey(); ok { + _spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value) + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Config(); ok { + _spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value) + } + if value, ok := _u.mutation.SupportedTypes(); ok { + _spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value) + } + if value, ok := _u.mutation.Enabled(); ok { + _spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value) + } + if value, ok := _u.mutation.PaymentMode(); ok { + _spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.Limits(); ok { + _spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value) + } + if value, ok := _u.mutation.RefundEnabled(); ok { + _spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value) + } + _node = &PaymentProviderInstance{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{paymentproviderinstance.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/predicate/predicate.go b/backend/ent/predicate/predicate.go index a652ab3f1be8dfbe32ba5da438390b5db294f66c..ef551940067ec1a2d96783ca78994bc6cb8d9fae 100644 --- a/backend/ent/predicate/predicate.go +++ b/backend/ent/predicate/predicate.go @@ -30,6 +30,15 @@ type Group func(*sql.Selector) // IdempotencyRecord is the predicate function for idempotencyrecord builders. type IdempotencyRecord func(*sql.Selector) +// PaymentAuditLog is the predicate function for paymentauditlog builders. +type PaymentAuditLog func(*sql.Selector) + +// PaymentOrder is the predicate function for paymentorder builders. +type PaymentOrder func(*sql.Selector) + +// PaymentProviderInstance is the predicate function for paymentproviderinstance builders. +type PaymentProviderInstance func(*sql.Selector) + // PromoCode is the predicate function for promocode builders. type PromoCode func(*sql.Selector) @@ -48,6 +57,9 @@ type SecuritySecret func(*sql.Selector) // Setting is the predicate function for setting builders. type Setting func(*sql.Selector) +// SubscriptionPlan is the predicate function for subscriptionplan builders. +type SubscriptionPlan func(*sql.Selector) + // TLSFingerprintProfile is the predicate function for tlsfingerprintprofile builders. type TLSFingerprintProfile func(*sql.Selector) diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go index 803b7bc24b4552b9b07d70726a56efa261d1cc32..821b7d66e0d80e9889d614b629b6b759735e7965 100644 --- a/backend/ent/runtime/runtime.go +++ b/backend/ent/runtime/runtime.go @@ -13,6 +13,9 @@ import ( "github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule" "github.com/Wei-Shaw/sub2api/ent/group" "github.com/Wei-Shaw/sub2api/ent/idempotencyrecord" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" "github.com/Wei-Shaw/sub2api/ent/promocode" "github.com/Wei-Shaw/sub2api/ent/promocodeusage" "github.com/Wei-Shaw/sub2api/ent/proxy" @@ -20,6 +23,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/schema" "github.com/Wei-Shaw/sub2api/ent/securitysecret" "github.com/Wei-Shaw/sub2api/ent/setting" + "github.com/Wei-Shaw/sub2api/ent/subscriptionplan" "github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile" "github.com/Wei-Shaw/sub2api/ent/usagecleanuptask" "github.com/Wei-Shaw/sub2api/ent/usagelog" @@ -28,6 +32,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/userattributedefinition" "github.com/Wei-Shaw/sub2api/ent/userattributevalue" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/internal/domain" ) // The init function reads all schema descriptors with runtime code @@ -468,6 +473,10 @@ func init() { group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string) // group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save. group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error) + // groupDescMessagesDispatchModelConfig is the schema descriptor for messages_dispatch_model_config field. + groupDescMessagesDispatchModelConfig := groupFields[26].Descriptor() + // group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field. + group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig) idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin() idempotencyrecordMixinFields0 := idempotencyrecordMixin[0].Fields() _ = idempotencyrecordMixinFields0 @@ -503,6 +512,172 @@ func init() { idempotencyrecordDescErrorReason := idempotencyrecordFields[6].Descriptor() // idempotencyrecord.ErrorReasonValidator is a validator for the "error_reason" field. It is called by the builders before save. idempotencyrecord.ErrorReasonValidator = idempotencyrecordDescErrorReason.Validators[0].(func(string) error) + paymentauditlogFields := schema.PaymentAuditLog{}.Fields() + _ = paymentauditlogFields + // paymentauditlogDescOrderID is the schema descriptor for order_id field. + paymentauditlogDescOrderID := paymentauditlogFields[0].Descriptor() + // paymentauditlog.OrderIDValidator is a validator for the "order_id" field. It is called by the builders before save. + paymentauditlog.OrderIDValidator = paymentauditlogDescOrderID.Validators[0].(func(string) error) + // paymentauditlogDescAction is the schema descriptor for action field. + paymentauditlogDescAction := paymentauditlogFields[1].Descriptor() + // paymentauditlog.ActionValidator is a validator for the "action" field. It is called by the builders before save. + paymentauditlog.ActionValidator = paymentauditlogDescAction.Validators[0].(func(string) error) + // paymentauditlogDescDetail is the schema descriptor for detail field. + paymentauditlogDescDetail := paymentauditlogFields[2].Descriptor() + // paymentauditlog.DefaultDetail holds the default value on creation for the detail field. + paymentauditlog.DefaultDetail = paymentauditlogDescDetail.Default.(string) + // paymentauditlogDescOperator is the schema descriptor for operator field. + paymentauditlogDescOperator := paymentauditlogFields[3].Descriptor() + // paymentauditlog.DefaultOperator holds the default value on creation for the operator field. + paymentauditlog.DefaultOperator = paymentauditlogDescOperator.Default.(string) + // paymentauditlog.OperatorValidator is a validator for the "operator" field. It is called by the builders before save. + paymentauditlog.OperatorValidator = paymentauditlogDescOperator.Validators[0].(func(string) error) + // paymentauditlogDescCreatedAt is the schema descriptor for created_at field. + paymentauditlogDescCreatedAt := paymentauditlogFields[4].Descriptor() + // paymentauditlog.DefaultCreatedAt holds the default value on creation for the created_at field. + paymentauditlog.DefaultCreatedAt = paymentauditlogDescCreatedAt.Default.(func() time.Time) + paymentorderFields := schema.PaymentOrder{}.Fields() + _ = paymentorderFields + // paymentorderDescUserEmail is the schema descriptor for user_email field. + paymentorderDescUserEmail := paymentorderFields[1].Descriptor() + // paymentorder.UserEmailValidator is a validator for the "user_email" field. It is called by the builders before save. + paymentorder.UserEmailValidator = paymentorderDescUserEmail.Validators[0].(func(string) error) + // paymentorderDescUserName is the schema descriptor for user_name field. + paymentorderDescUserName := paymentorderFields[2].Descriptor() + // paymentorder.UserNameValidator is a validator for the "user_name" field. It is called by the builders before save. + paymentorder.UserNameValidator = paymentorderDescUserName.Validators[0].(func(string) error) + // paymentorderDescFeeRate is the schema descriptor for fee_rate field. + paymentorderDescFeeRate := paymentorderFields[6].Descriptor() + // paymentorder.DefaultFeeRate holds the default value on creation for the fee_rate field. + paymentorder.DefaultFeeRate = paymentorderDescFeeRate.Default.(float64) + // paymentorderDescRechargeCode is the schema descriptor for recharge_code field. + paymentorderDescRechargeCode := paymentorderFields[7].Descriptor() + // paymentorder.RechargeCodeValidator is a validator for the "recharge_code" field. It is called by the builders before save. + paymentorder.RechargeCodeValidator = paymentorderDescRechargeCode.Validators[0].(func(string) error) + // paymentorderDescOutTradeNo is the schema descriptor for out_trade_no field. + paymentorderDescOutTradeNo := paymentorderFields[8].Descriptor() + // paymentorder.DefaultOutTradeNo holds the default value on creation for the out_trade_no field. + paymentorder.DefaultOutTradeNo = paymentorderDescOutTradeNo.Default.(string) + // paymentorder.OutTradeNoValidator is a validator for the "out_trade_no" field. It is called by the builders before save. + paymentorder.OutTradeNoValidator = paymentorderDescOutTradeNo.Validators[0].(func(string) error) + // paymentorderDescPaymentType is the schema descriptor for payment_type field. + paymentorderDescPaymentType := paymentorderFields[9].Descriptor() + // paymentorder.PaymentTypeValidator is a validator for the "payment_type" field. It is called by the builders before save. + paymentorder.PaymentTypeValidator = paymentorderDescPaymentType.Validators[0].(func(string) error) + // paymentorderDescPaymentTradeNo is the schema descriptor for payment_trade_no field. + paymentorderDescPaymentTradeNo := paymentorderFields[10].Descriptor() + // paymentorder.PaymentTradeNoValidator is a validator for the "payment_trade_no" field. It is called by the builders before save. + paymentorder.PaymentTradeNoValidator = paymentorderDescPaymentTradeNo.Validators[0].(func(string) error) + // paymentorderDescOrderType is the schema descriptor for order_type field. + paymentorderDescOrderType := paymentorderFields[14].Descriptor() + // paymentorder.DefaultOrderType holds the default value on creation for the order_type field. + paymentorder.DefaultOrderType = paymentorderDescOrderType.Default.(string) + // paymentorder.OrderTypeValidator is a validator for the "order_type" field. It is called by the builders before save. + paymentorder.OrderTypeValidator = paymentorderDescOrderType.Validators[0].(func(string) error) + // paymentorderDescProviderInstanceID is the schema descriptor for provider_instance_id field. + paymentorderDescProviderInstanceID := paymentorderFields[18].Descriptor() + // paymentorder.ProviderInstanceIDValidator is a validator for the "provider_instance_id" field. It is called by the builders before save. + paymentorder.ProviderInstanceIDValidator = paymentorderDescProviderInstanceID.Validators[0].(func(string) error) + // paymentorderDescStatus is the schema descriptor for status field. + paymentorderDescStatus := paymentorderFields[19].Descriptor() + // paymentorder.DefaultStatus holds the default value on creation for the status field. + paymentorder.DefaultStatus = paymentorderDescStatus.Default.(string) + // paymentorder.StatusValidator is a validator for the "status" field. It is called by the builders before save. + paymentorder.StatusValidator = paymentorderDescStatus.Validators[0].(func(string) error) + // paymentorderDescRefundAmount is the schema descriptor for refund_amount field. + paymentorderDescRefundAmount := paymentorderFields[20].Descriptor() + // paymentorder.DefaultRefundAmount holds the default value on creation for the refund_amount field. + paymentorder.DefaultRefundAmount = paymentorderDescRefundAmount.Default.(float64) + // paymentorderDescForceRefund is the schema descriptor for force_refund field. + paymentorderDescForceRefund := paymentorderFields[23].Descriptor() + // paymentorder.DefaultForceRefund holds the default value on creation for the force_refund field. + paymentorder.DefaultForceRefund = paymentorderDescForceRefund.Default.(bool) + // paymentorderDescRefundRequestedBy is the schema descriptor for refund_requested_by field. + paymentorderDescRefundRequestedBy := paymentorderFields[26].Descriptor() + // paymentorder.RefundRequestedByValidator is a validator for the "refund_requested_by" field. It is called by the builders before save. + paymentorder.RefundRequestedByValidator = paymentorderDescRefundRequestedBy.Validators[0].(func(string) error) + // paymentorderDescClientIP is the schema descriptor for client_ip field. + paymentorderDescClientIP := paymentorderFields[32].Descriptor() + // paymentorder.ClientIPValidator is a validator for the "client_ip" field. It is called by the builders before save. + paymentorder.ClientIPValidator = paymentorderDescClientIP.Validators[0].(func(string) error) + // paymentorderDescSrcHost is the schema descriptor for src_host field. + paymentorderDescSrcHost := paymentorderFields[33].Descriptor() + // paymentorder.SrcHostValidator is a validator for the "src_host" field. It is called by the builders before save. + paymentorder.SrcHostValidator = paymentorderDescSrcHost.Validators[0].(func(string) error) + // paymentorderDescCreatedAt is the schema descriptor for created_at field. + paymentorderDescCreatedAt := paymentorderFields[35].Descriptor() + // paymentorder.DefaultCreatedAt holds the default value on creation for the created_at field. + paymentorder.DefaultCreatedAt = paymentorderDescCreatedAt.Default.(func() time.Time) + // paymentorderDescUpdatedAt is the schema descriptor for updated_at field. + paymentorderDescUpdatedAt := paymentorderFields[36].Descriptor() + // paymentorder.DefaultUpdatedAt holds the default value on creation for the updated_at field. + paymentorder.DefaultUpdatedAt = paymentorderDescUpdatedAt.Default.(func() time.Time) + // paymentorder.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + paymentorder.UpdateDefaultUpdatedAt = paymentorderDescUpdatedAt.UpdateDefault.(func() time.Time) + paymentproviderinstanceFields := schema.PaymentProviderInstance{}.Fields() + _ = paymentproviderinstanceFields + // paymentproviderinstanceDescProviderKey is the schema descriptor for provider_key field. + paymentproviderinstanceDescProviderKey := paymentproviderinstanceFields[0].Descriptor() + // paymentproviderinstance.ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save. + paymentproviderinstance.ProviderKeyValidator = func() func(string) error { + validators := paymentproviderinstanceDescProviderKey.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(provider_key string) error { + for _, fn := range fns { + if err := fn(provider_key); err != nil { + return err + } + } + return nil + } + }() + // paymentproviderinstanceDescName is the schema descriptor for name field. + paymentproviderinstanceDescName := paymentproviderinstanceFields[1].Descriptor() + // paymentproviderinstance.DefaultName holds the default value on creation for the name field. + paymentproviderinstance.DefaultName = paymentproviderinstanceDescName.Default.(string) + // paymentproviderinstance.NameValidator is a validator for the "name" field. It is called by the builders before save. + paymentproviderinstance.NameValidator = paymentproviderinstanceDescName.Validators[0].(func(string) error) + // paymentproviderinstanceDescSupportedTypes is the schema descriptor for supported_types field. + paymentproviderinstanceDescSupportedTypes := paymentproviderinstanceFields[3].Descriptor() + // paymentproviderinstance.DefaultSupportedTypes holds the default value on creation for the supported_types field. + paymentproviderinstance.DefaultSupportedTypes = paymentproviderinstanceDescSupportedTypes.Default.(string) + // paymentproviderinstance.SupportedTypesValidator is a validator for the "supported_types" field. It is called by the builders before save. + paymentproviderinstance.SupportedTypesValidator = paymentproviderinstanceDescSupportedTypes.Validators[0].(func(string) error) + // paymentproviderinstanceDescEnabled is the schema descriptor for enabled field. + paymentproviderinstanceDescEnabled := paymentproviderinstanceFields[4].Descriptor() + // paymentproviderinstance.DefaultEnabled holds the default value on creation for the enabled field. + paymentproviderinstance.DefaultEnabled = paymentproviderinstanceDescEnabled.Default.(bool) + // paymentproviderinstanceDescPaymentMode is the schema descriptor for payment_mode field. + paymentproviderinstanceDescPaymentMode := paymentproviderinstanceFields[5].Descriptor() + // paymentproviderinstance.DefaultPaymentMode holds the default value on creation for the payment_mode field. + paymentproviderinstance.DefaultPaymentMode = paymentproviderinstanceDescPaymentMode.Default.(string) + // paymentproviderinstance.PaymentModeValidator is a validator for the "payment_mode" field. It is called by the builders before save. + paymentproviderinstance.PaymentModeValidator = paymentproviderinstanceDescPaymentMode.Validators[0].(func(string) error) + // paymentproviderinstanceDescSortOrder is the schema descriptor for sort_order field. + paymentproviderinstanceDescSortOrder := paymentproviderinstanceFields[6].Descriptor() + // paymentproviderinstance.DefaultSortOrder holds the default value on creation for the sort_order field. + paymentproviderinstance.DefaultSortOrder = paymentproviderinstanceDescSortOrder.Default.(int) + // paymentproviderinstanceDescLimits is the schema descriptor for limits field. + paymentproviderinstanceDescLimits := paymentproviderinstanceFields[7].Descriptor() + // paymentproviderinstance.DefaultLimits holds the default value on creation for the limits field. + paymentproviderinstance.DefaultLimits = paymentproviderinstanceDescLimits.Default.(string) + // paymentproviderinstanceDescRefundEnabled is the schema descriptor for refund_enabled field. + paymentproviderinstanceDescRefundEnabled := paymentproviderinstanceFields[8].Descriptor() + // paymentproviderinstance.DefaultRefundEnabled holds the default value on creation for the refund_enabled field. + paymentproviderinstance.DefaultRefundEnabled = paymentproviderinstanceDescRefundEnabled.Default.(bool) + // paymentproviderinstanceDescCreatedAt is the schema descriptor for created_at field. + paymentproviderinstanceDescCreatedAt := paymentproviderinstanceFields[9].Descriptor() + // paymentproviderinstance.DefaultCreatedAt holds the default value on creation for the created_at field. + paymentproviderinstance.DefaultCreatedAt = paymentproviderinstanceDescCreatedAt.Default.(func() time.Time) + // paymentproviderinstanceDescUpdatedAt is the schema descriptor for updated_at field. + paymentproviderinstanceDescUpdatedAt := paymentproviderinstanceFields[10].Descriptor() + // paymentproviderinstance.DefaultUpdatedAt holds the default value on creation for the updated_at field. + paymentproviderinstance.DefaultUpdatedAt = paymentproviderinstanceDescUpdatedAt.Default.(func() time.Time) + // paymentproviderinstance.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + paymentproviderinstance.UpdateDefaultUpdatedAt = paymentproviderinstanceDescUpdatedAt.UpdateDefault.(func() time.Time) promocodeFields := schema.PromoCode{}.Fields() _ = promocodeFields // promocodeDescCode is the schema descriptor for code field. @@ -751,6 +926,68 @@ func init() { setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time) // setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time) + subscriptionplanFields := schema.SubscriptionPlan{}.Fields() + _ = subscriptionplanFields + // subscriptionplanDescName is the schema descriptor for name field. + subscriptionplanDescName := subscriptionplanFields[1].Descriptor() + // subscriptionplan.NameValidator is a validator for the "name" field. It is called by the builders before save. + subscriptionplan.NameValidator = func() func(string) error { + validators := subscriptionplanDescName.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + } + return func(name string) error { + for _, fn := range fns { + if err := fn(name); err != nil { + return err + } + } + return nil + } + }() + // subscriptionplanDescDescription is the schema descriptor for description field. + subscriptionplanDescDescription := subscriptionplanFields[2].Descriptor() + // subscriptionplan.DefaultDescription holds the default value on creation for the description field. + subscriptionplan.DefaultDescription = subscriptionplanDescDescription.Default.(string) + // subscriptionplanDescValidityDays is the schema descriptor for validity_days field. + subscriptionplanDescValidityDays := subscriptionplanFields[5].Descriptor() + // subscriptionplan.DefaultValidityDays holds the default value on creation for the validity_days field. + subscriptionplan.DefaultValidityDays = subscriptionplanDescValidityDays.Default.(int) + // subscriptionplanDescValidityUnit is the schema descriptor for validity_unit field. + subscriptionplanDescValidityUnit := subscriptionplanFields[6].Descriptor() + // subscriptionplan.DefaultValidityUnit holds the default value on creation for the validity_unit field. + subscriptionplan.DefaultValidityUnit = subscriptionplanDescValidityUnit.Default.(string) + // subscriptionplan.ValidityUnitValidator is a validator for the "validity_unit" field. It is called by the builders before save. + subscriptionplan.ValidityUnitValidator = subscriptionplanDescValidityUnit.Validators[0].(func(string) error) + // subscriptionplanDescFeatures is the schema descriptor for features field. + subscriptionplanDescFeatures := subscriptionplanFields[7].Descriptor() + // subscriptionplan.DefaultFeatures holds the default value on creation for the features field. + subscriptionplan.DefaultFeatures = subscriptionplanDescFeatures.Default.(string) + // subscriptionplanDescProductName is the schema descriptor for product_name field. + subscriptionplanDescProductName := subscriptionplanFields[8].Descriptor() + // subscriptionplan.DefaultProductName holds the default value on creation for the product_name field. + subscriptionplan.DefaultProductName = subscriptionplanDescProductName.Default.(string) + // subscriptionplan.ProductNameValidator is a validator for the "product_name" field. It is called by the builders before save. + subscriptionplan.ProductNameValidator = subscriptionplanDescProductName.Validators[0].(func(string) error) + // subscriptionplanDescForSale is the schema descriptor for for_sale field. + subscriptionplanDescForSale := subscriptionplanFields[9].Descriptor() + // subscriptionplan.DefaultForSale holds the default value on creation for the for_sale field. + subscriptionplan.DefaultForSale = subscriptionplanDescForSale.Default.(bool) + // subscriptionplanDescSortOrder is the schema descriptor for sort_order field. + subscriptionplanDescSortOrder := subscriptionplanFields[10].Descriptor() + // subscriptionplan.DefaultSortOrder holds the default value on creation for the sort_order field. + subscriptionplan.DefaultSortOrder = subscriptionplanDescSortOrder.Default.(int) + // subscriptionplanDescCreatedAt is the schema descriptor for created_at field. + subscriptionplanDescCreatedAt := subscriptionplanFields[11].Descriptor() + // subscriptionplan.DefaultCreatedAt holds the default value on creation for the created_at field. + subscriptionplan.DefaultCreatedAt = subscriptionplanDescCreatedAt.Default.(func() time.Time) + // subscriptionplanDescUpdatedAt is the schema descriptor for updated_at field. + subscriptionplanDescUpdatedAt := subscriptionplanFields[12].Descriptor() + // subscriptionplan.DefaultUpdatedAt holds the default value on creation for the updated_at field. + subscriptionplan.DefaultUpdatedAt = subscriptionplanDescUpdatedAt.Default.(func() time.Time) + // subscriptionplan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + subscriptionplan.UpdateDefaultUpdatedAt = subscriptionplanDescUpdatedAt.UpdateDefault.(func() time.Time) tlsfingerprintprofileMixin := schema.TLSFingerprintProfile{}.Mixin() tlsfingerprintprofileMixinFields0 := tlsfingerprintprofileMixin[0].Fields() _ = tlsfingerprintprofileMixinFields0 diff --git a/backend/ent/schema/group.go b/backend/ent/schema/group.go index 0eb89c181bbaf65fef4658763b2234afe381f149..d78a68986dea728ef984ecd709c84e7636722d3f 100644 --- a/backend/ent/schema/group.go +++ b/backend/ent/schema/group.go @@ -141,6 +141,10 @@ func (Group) Fields() []ent.Field { MaxLen(100). Default(""). Comment("默认映射模型 ID,当账号级映射找不到时使用此值"), + field.JSON("messages_dispatch_model_config", domain.OpenAIMessagesDispatchModelConfig{}). + Default(domain.OpenAIMessagesDispatchModelConfig{}). + SchemaType(map[string]string{dialect.Postgres: "jsonb"}). + Comment("OpenAI Messages 调度模型配置:按 Claude 系列/精确模型映射到目标 GPT 模型"), } } diff --git a/backend/ent/schema/payment_audit_log.go b/backend/ent/schema/payment_audit_log.go new file mode 100644 index 0000000000000000000000000000000000000000..7f8a8c04423bf35a8ff1d4711e6aac2b993615f9 --- /dev/null +++ b/backend/ent/schema/payment_audit_log.go @@ -0,0 +1,54 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/entsql" + "entgo.io/ent/schema" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// PaymentAuditLog holds the schema definition for the PaymentAuditLog entity. +// +// 删除策略:硬删除 +// PaymentAuditLog 使用硬删除而非软删除,原因如下: +// - 审计日志本身即为不可变记录,通常只追加不修改 +// - 如需清理历史日志,直接按时间范围批量删除即可 +// - 保持表结构简洁,提升插入和查询性能 +type PaymentAuditLog struct { + ent.Schema +} + +func (PaymentAuditLog) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "payment_audit_logs"}, + } +} + +func (PaymentAuditLog) Fields() []ent.Field { + return []ent.Field{ + field.String("order_id"). + MaxLen(64), + field.String("action"). + MaxLen(50), + field.String("detail"). + SchemaType(map[string]string{dialect.Postgres: "text"}). + Default(""), + field.String("operator"). + MaxLen(100). + Default("system"), + field.Time("created_at"). + Immutable(). + Default(time.Now). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + } +} + +func (PaymentAuditLog) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("order_id"), + } +} diff --git a/backend/ent/schema/payment_order.go b/backend/ent/schema/payment_order.go new file mode 100644 index 0000000000000000000000000000000000000000..a9576d2ab02aca442249de0457ea4321cf15f85e --- /dev/null +++ b/backend/ent/schema/payment_order.go @@ -0,0 +1,190 @@ +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" +) + +// PaymentOrder holds the schema definition for the PaymentOrder entity. +// +// 删除策略:硬删除 +// PaymentOrder 使用硬删除而非软删除,原因如下: +// - 订单通过 status 字段追踪完整生命周期,无需依赖软删除 +// - 订单审计通过 PaymentAuditLog 表记录,删除前可归档 +// - 减少查询复杂度,避免软删除过滤开销 +type PaymentOrder struct { + ent.Schema +} + +func (PaymentOrder) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "payment_orders"}, + } +} + +func (PaymentOrder) Fields() []ent.Field { + return []ent.Field{ + // 用户信息(冗余存储,避免关联查询) + field.Int64("user_id"), + field.String("user_email"). + MaxLen(255), + field.String("user_name"). + MaxLen(100), + field.String("user_notes"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + + // 金额信息 + field.Float("amount"). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}), + field.Float("pay_amount"). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}), + field.Float("fee_rate"). + SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}). + Default(0), + field.String("recharge_code"). + MaxLen(64), + + // 支付信息 + field.String("out_trade_no"). + MaxLen(64). + Default(""), + field.String("payment_type"). + MaxLen(30), + field.String("payment_trade_no"). + MaxLen(128), + field.String("pay_url"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + field.String("qr_code"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + field.String("qr_code_img"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + + // 订单类型 & 订阅关联 + field.String("order_type"). + MaxLen(20). + Default("balance"), + field.Int64("plan_id"). + Optional(). + Nillable(), + field.Int64("subscription_group_id"). + Optional(). + Nillable(), + field.Int("subscription_days"). + Optional(). + Nillable(), + field.String("provider_instance_id"). + Optional(). + Nillable(). + MaxLen(64), + + // 状态 + field.String("status"). + MaxLen(30). + Default("PENDING"), + + // 退款信息 + field.Float("refund_amount"). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}). + Default(0), + field.String("refund_reason"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + field.Time("refund_at"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.Bool("force_refund"). + Default(false), + field.Time("refund_requested_at"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.String("refund_request_reason"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + field.String("refund_requested_by"). + Optional(). + Nillable(). + MaxLen(20), + + // 时间节点 + field.Time("expires_at"). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.Time("paid_at"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.Time("completed_at"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.Time("failed_at"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.String("failed_reason"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + + // 来源信息 + field.String("client_ip"). + MaxLen(50), + field.String("src_host"). + MaxLen(255), + field.String("src_url"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + + // 时间戳 + 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 (PaymentOrder) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("user", User.Type). + Ref("payment_orders"). + Field("user_id"). + Unique(). + Required(), + } +} + +func (PaymentOrder) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("out_trade_no"), + index.Fields("user_id"), + index.Fields("status"), + index.Fields("expires_at"), + index.Fields("created_at"), + index.Fields("paid_at"), + index.Fields("payment_type", "paid_at"), + index.Fields("order_type"), + } +} diff --git a/backend/ent/schema/payment_provider_instance.go b/backend/ent/schema/payment_provider_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..08ab7d31c75dfb513aaf7b5a19d7b538275e214d --- /dev/null +++ b/backend/ent/schema/payment_provider_instance.go @@ -0,0 +1,72 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/entsql" + "entgo.io/ent/schema" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// PaymentProviderInstance holds the schema definition for the PaymentProviderInstance entity. +// +// 删除策略:硬删除 +// PaymentProviderInstance 使用硬删除而非软删除,原因如下: +// - 服务商实例为管理员配置的支付通道,删除即表示废弃 +// - 通过 enabled 字段控制是否启用,删除仅用于彻底移除 +// - config 字段存储加密后的密钥信息,删除时应彻底清除 +type PaymentProviderInstance struct { + ent.Schema +} + +func (PaymentProviderInstance) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "payment_provider_instances"}, + } +} + +func (PaymentProviderInstance) Fields() []ent.Field { + return []ent.Field{ + field.String("provider_key"). + MaxLen(30). + NotEmpty(), + field.String("name"). + MaxLen(100). + Default(""), + field.String("config"). + SchemaType(map[string]string{dialect.Postgres: "text"}), + field.String("supported_types"). + MaxLen(200). + Default(""), + field.Bool("enabled"). + Default(true), + field.String("payment_mode"). + MaxLen(20). + Default(""), + field.Int("sort_order"). + Default(0), + field.String("limits"). + SchemaType(map[string]string{dialect.Postgres: "text"}). + Default(""), + field.Bool("refund_enabled"). + Default(false), + 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 (PaymentProviderInstance) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("provider_key"), + index.Fields("enabled"), + } +} diff --git a/backend/ent/schema/subscription_plan.go b/backend/ent/schema/subscription_plan.go new file mode 100644 index 0000000000000000000000000000000000000000..3e30490bb248181c0c31a95e48f36d9ce7628164 --- /dev/null +++ b/backend/ent/schema/subscription_plan.go @@ -0,0 +1,77 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/entsql" + "entgo.io/ent/schema" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// SubscriptionPlan holds the schema definition for the SubscriptionPlan entity. +// +// 删除策略:硬删除 +// SubscriptionPlan 使用硬删除而非软删除,原因如下: +// - 套餐为管理员维护的商品配置,删除即表示下架移除 +// - 通过 for_sale 字段控制是否在售,删除仅用于彻底移除 +// - 已购买的订阅记录保存在 UserSubscription 中,不受套餐删除影响 +type SubscriptionPlan struct { + ent.Schema +} + +func (SubscriptionPlan) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "subscription_plans"}, + } +} + +func (SubscriptionPlan) Fields() []ent.Field { + return []ent.Field{ + field.Int64("group_id"), + field.String("name"). + MaxLen(100). + NotEmpty(), + field.String("description"). + SchemaType(map[string]string{dialect.Postgres: "text"}). + Default(""), + field.Float("price"). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}), + field.Float("original_price"). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}). + Optional(). + Nillable(), + field.Int("validity_days"). + Default(30), + field.String("validity_unit"). + MaxLen(10). + Default("day"), + field.String("features"). + SchemaType(map[string]string{dialect.Postgres: "text"}). + Default(""), + field.String("product_name"). + MaxLen(100). + Default(""), + field.Bool("for_sale"). + Default(true), + field.Int("sort_order"). + Default(0), + 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 (SubscriptionPlan) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("group_id"), + index.Fields("for_sale"), + } +} diff --git a/backend/ent/schema/user.go b/backend/ent/schema/user.go index d443ef455c1989001c66acf8ebf0dd7929aa2a24..af143d38ed92d31947ec2edc2d6a64a4a37e6f66 100644 --- a/backend/ent/schema/user.go +++ b/backend/ent/schema/user.go @@ -87,6 +87,7 @@ func (User) Edges() []ent.Edge { edge.To("usage_logs", UsageLog.Type), edge.To("attribute_values", UserAttributeValue.Type), edge.To("promo_code_usages", PromoCodeUsage.Type), + edge.To("payment_orders", PaymentOrder.Type), } } diff --git a/backend/ent/subscriptionplan.go b/backend/ent/subscriptionplan.go new file mode 100644 index 0000000000000000000000000000000000000000..fa4d7ae3b57463ca49bbc43e22fce335ceaf83f6 --- /dev/null +++ b/backend/ent/subscriptionplan.go @@ -0,0 +1,245 @@ +// 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/subscriptionplan" +) + +// SubscriptionPlan is the model entity for the SubscriptionPlan schema. +type SubscriptionPlan struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // GroupID holds the value of the "group_id" field. + GroupID int64 `json:"group_id,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` + // Description holds the value of the "description" field. + Description string `json:"description,omitempty"` + // Price holds the value of the "price" field. + Price float64 `json:"price,omitempty"` + // OriginalPrice holds the value of the "original_price" field. + OriginalPrice *float64 `json:"original_price,omitempty"` + // ValidityDays holds the value of the "validity_days" field. + ValidityDays int `json:"validity_days,omitempty"` + // ValidityUnit holds the value of the "validity_unit" field. + ValidityUnit string `json:"validity_unit,omitempty"` + // Features holds the value of the "features" field. + Features string `json:"features,omitempty"` + // ProductName holds the value of the "product_name" field. + ProductName string `json:"product_name,omitempty"` + // ForSale holds the value of the "for_sale" field. + ForSale bool `json:"for_sale,omitempty"` + // SortOrder holds the value of the "sort_order" field. + SortOrder int `json:"sort_order,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"` + selectValues sql.SelectValues +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*SubscriptionPlan) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case subscriptionplan.FieldForSale: + values[i] = new(sql.NullBool) + case subscriptionplan.FieldPrice, subscriptionplan.FieldOriginalPrice: + values[i] = new(sql.NullFloat64) + case subscriptionplan.FieldID, subscriptionplan.FieldGroupID, subscriptionplan.FieldValidityDays, subscriptionplan.FieldSortOrder: + values[i] = new(sql.NullInt64) + case subscriptionplan.FieldName, subscriptionplan.FieldDescription, subscriptionplan.FieldValidityUnit, subscriptionplan.FieldFeatures, subscriptionplan.FieldProductName: + values[i] = new(sql.NullString) + case subscriptionplan.FieldCreatedAt, subscriptionplan.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 SubscriptionPlan fields. +func (_m *SubscriptionPlan) 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 subscriptionplan.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 subscriptionplan.FieldGroupID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field group_id", values[i]) + } else if value.Valid { + _m.GroupID = value.Int64 + } + case subscriptionplan.FieldName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field name", values[i]) + } else if value.Valid { + _m.Name = value.String + } + case subscriptionplan.FieldDescription: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field description", values[i]) + } else if value.Valid { + _m.Description = value.String + } + case subscriptionplan.FieldPrice: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field price", values[i]) + } else if value.Valid { + _m.Price = value.Float64 + } + case subscriptionplan.FieldOriginalPrice: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field original_price", values[i]) + } else if value.Valid { + _m.OriginalPrice = new(float64) + *_m.OriginalPrice = value.Float64 + } + case subscriptionplan.FieldValidityDays: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field validity_days", values[i]) + } else if value.Valid { + _m.ValidityDays = int(value.Int64) + } + case subscriptionplan.FieldValidityUnit: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field validity_unit", values[i]) + } else if value.Valid { + _m.ValidityUnit = value.String + } + case subscriptionplan.FieldFeatures: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field features", values[i]) + } else if value.Valid { + _m.Features = value.String + } + case subscriptionplan.FieldProductName: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field product_name", values[i]) + } else if value.Valid { + _m.ProductName = value.String + } + case subscriptionplan.FieldForSale: + if value, ok := values[i].(*sql.NullBool); !ok { + return fmt.Errorf("unexpected type %T for field for_sale", values[i]) + } else if value.Valid { + _m.ForSale = value.Bool + } + case subscriptionplan.FieldSortOrder: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field sort_order", values[i]) + } else if value.Valid { + _m.SortOrder = int(value.Int64) + } + case subscriptionplan.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 subscriptionplan.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 SubscriptionPlan. +// This includes values selected through modifiers, order, etc. +func (_m *SubscriptionPlan) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// Update returns a builder for updating this SubscriptionPlan. +// Note that you need to call SubscriptionPlan.Unwrap() before calling this method if this SubscriptionPlan +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *SubscriptionPlan) Update() *SubscriptionPlanUpdateOne { + return NewSubscriptionPlanClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the SubscriptionPlan 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 *SubscriptionPlan) Unwrap() *SubscriptionPlan { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: SubscriptionPlan is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *SubscriptionPlan) String() string { + var builder strings.Builder + builder.WriteString("SubscriptionPlan(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("group_id=") + builder.WriteString(fmt.Sprintf("%v", _m.GroupID)) + builder.WriteString(", ") + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteString(", ") + builder.WriteString("description=") + builder.WriteString(_m.Description) + builder.WriteString(", ") + builder.WriteString("price=") + builder.WriteString(fmt.Sprintf("%v", _m.Price)) + builder.WriteString(", ") + if v := _m.OriginalPrice; v != nil { + builder.WriteString("original_price=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") + builder.WriteString("validity_days=") + builder.WriteString(fmt.Sprintf("%v", _m.ValidityDays)) + builder.WriteString(", ") + builder.WriteString("validity_unit=") + builder.WriteString(_m.ValidityUnit) + builder.WriteString(", ") + builder.WriteString("features=") + builder.WriteString(_m.Features) + builder.WriteString(", ") + builder.WriteString("product_name=") + builder.WriteString(_m.ProductName) + builder.WriteString(", ") + builder.WriteString("for_sale=") + builder.WriteString(fmt.Sprintf("%v", _m.ForSale)) + builder.WriteString(", ") + builder.WriteString("sort_order=") + builder.WriteString(fmt.Sprintf("%v", _m.SortOrder)) + 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() +} + +// SubscriptionPlans is a parsable slice of SubscriptionPlan. +type SubscriptionPlans []*SubscriptionPlan diff --git a/backend/ent/subscriptionplan/subscriptionplan.go b/backend/ent/subscriptionplan/subscriptionplan.go new file mode 100644 index 0000000000000000000000000000000000000000..fa125aa714cd4698f60df4b3679a7bcb19e92c7d --- /dev/null +++ b/backend/ent/subscriptionplan/subscriptionplan.go @@ -0,0 +1,174 @@ +// Code generated by ent, DO NOT EDIT. + +package subscriptionplan + +import ( + "time" + + "entgo.io/ent/dialect/sql" +) + +const ( + // Label holds the string label denoting the subscriptionplan type in the database. + Label = "subscription_plan" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldGroupID holds the string denoting the group_id field in the database. + FieldGroupID = "group_id" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldDescription holds the string denoting the description field in the database. + FieldDescription = "description" + // FieldPrice holds the string denoting the price field in the database. + FieldPrice = "price" + // FieldOriginalPrice holds the string denoting the original_price field in the database. + FieldOriginalPrice = "original_price" + // FieldValidityDays holds the string denoting the validity_days field in the database. + FieldValidityDays = "validity_days" + // FieldValidityUnit holds the string denoting the validity_unit field in the database. + FieldValidityUnit = "validity_unit" + // FieldFeatures holds the string denoting the features field in the database. + FieldFeatures = "features" + // FieldProductName holds the string denoting the product_name field in the database. + FieldProductName = "product_name" + // FieldForSale holds the string denoting the for_sale field in the database. + FieldForSale = "for_sale" + // FieldSortOrder holds the string denoting the sort_order field in the database. + FieldSortOrder = "sort_order" + // 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" + // Table holds the table name of the subscriptionplan in the database. + Table = "subscription_plans" +) + +// Columns holds all SQL columns for subscriptionplan fields. +var Columns = []string{ + FieldID, + FieldGroupID, + FieldName, + FieldDescription, + FieldPrice, + FieldOriginalPrice, + FieldValidityDays, + FieldValidityUnit, + FieldFeatures, + FieldProductName, + FieldForSale, + FieldSortOrder, + 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 ( + // NameValidator is a validator for the "name" field. It is called by the builders before save. + NameValidator func(string) error + // DefaultDescription holds the default value on creation for the "description" field. + DefaultDescription string + // DefaultValidityDays holds the default value on creation for the "validity_days" field. + DefaultValidityDays int + // DefaultValidityUnit holds the default value on creation for the "validity_unit" field. + DefaultValidityUnit string + // ValidityUnitValidator is a validator for the "validity_unit" field. It is called by the builders before save. + ValidityUnitValidator func(string) error + // DefaultFeatures holds the default value on creation for the "features" field. + DefaultFeatures string + // DefaultProductName holds the default value on creation for the "product_name" field. + DefaultProductName string + // ProductNameValidator is a validator for the "product_name" field. It is called by the builders before save. + ProductNameValidator func(string) error + // DefaultForSale holds the default value on creation for the "for_sale" field. + DefaultForSale bool + // DefaultSortOrder holds the default value on creation for the "sort_order" field. + DefaultSortOrder int + // 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 SubscriptionPlan 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() +} + +// ByGroupID orders the results by the group_id field. +func ByGroupID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldGroupID, opts...).ToFunc() +} + +// ByName orders the results by the name field. +func ByName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldName, opts...).ToFunc() +} + +// ByDescription orders the results by the description field. +func ByDescription(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDescription, opts...).ToFunc() +} + +// ByPrice orders the results by the price field. +func ByPrice(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldPrice, opts...).ToFunc() +} + +// ByOriginalPrice orders the results by the original_price field. +func ByOriginalPrice(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldOriginalPrice, opts...).ToFunc() +} + +// ByValidityDays orders the results by the validity_days field. +func ByValidityDays(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldValidityDays, opts...).ToFunc() +} + +// ByValidityUnit orders the results by the validity_unit field. +func ByValidityUnit(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldValidityUnit, opts...).ToFunc() +} + +// ByFeatures orders the results by the features field. +func ByFeatures(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFeatures, opts...).ToFunc() +} + +// ByProductName orders the results by the product_name field. +func ByProductName(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldProductName, opts...).ToFunc() +} + +// ByForSale orders the results by the for_sale field. +func ByForSale(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldForSale, opts...).ToFunc() +} + +// BySortOrder orders the results by the sort_order field. +func BySortOrder(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSortOrder, 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() +} diff --git a/backend/ent/subscriptionplan/where.go b/backend/ent/subscriptionplan/where.go new file mode 100644 index 0000000000000000000000000000000000000000..319cfdb5095e2c8de736ed804228e3c96c1640e4 --- /dev/null +++ b/backend/ent/subscriptionplan/where.go @@ -0,0 +1,760 @@ +// Code generated by ent, DO NOT EDIT. + +package subscriptionplan + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldID, id)) +} + +// GroupID applies equality check predicate on the "group_id" field. It's identical to GroupIDEQ. +func GroupID(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldGroupID, v)) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldName, v)) +} + +// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ. +func Description(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldDescription, v)) +} + +// Price applies equality check predicate on the "price" field. It's identical to PriceEQ. +func Price(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldPrice, v)) +} + +// OriginalPrice applies equality check predicate on the "original_price" field. It's identical to OriginalPriceEQ. +func OriginalPrice(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldOriginalPrice, v)) +} + +// ValidityDays applies equality check predicate on the "validity_days" field. It's identical to ValidityDaysEQ. +func ValidityDays(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityDays, v)) +} + +// ValidityUnit applies equality check predicate on the "validity_unit" field. It's identical to ValidityUnitEQ. +func ValidityUnit(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityUnit, v)) +} + +// Features applies equality check predicate on the "features" field. It's identical to FeaturesEQ. +func Features(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldFeatures, v)) +} + +// ProductName applies equality check predicate on the "product_name" field. It's identical to ProductNameEQ. +func ProductName(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldProductName, v)) +} + +// ForSale applies equality check predicate on the "for_sale" field. It's identical to ForSaleEQ. +func ForSale(v bool) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldForSale, v)) +} + +// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ. +func SortOrder(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldSortOrder, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(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.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// GroupIDEQ applies the EQ predicate on the "group_id" field. +func GroupIDEQ(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldGroupID, v)) +} + +// GroupIDNEQ applies the NEQ predicate on the "group_id" field. +func GroupIDNEQ(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldGroupID, v)) +} + +// GroupIDIn applies the In predicate on the "group_id" field. +func GroupIDIn(vs ...int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldGroupID, vs...)) +} + +// GroupIDNotIn applies the NotIn predicate on the "group_id" field. +func GroupIDNotIn(vs ...int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldGroupID, vs...)) +} + +// GroupIDGT applies the GT predicate on the "group_id" field. +func GroupIDGT(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldGroupID, v)) +} + +// GroupIDGTE applies the GTE predicate on the "group_id" field. +func GroupIDGTE(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldGroupID, v)) +} + +// GroupIDLT applies the LT predicate on the "group_id" field. +func GroupIDLT(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldGroupID, v)) +} + +// GroupIDLTE applies the LTE predicate on the "group_id" field. +func GroupIDLTE(v int64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldGroupID, v)) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldName, v)) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldName, v)) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldName, vs...)) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldName, vs...)) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldName, v)) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldName, v)) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldName, v)) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldName, v)) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContains(FieldName, v)) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldName, v)) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldName, v)) +} + +// NameEqualFold applies the EqualFold predicate on the "name" field. +func NameEqualFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldName, v)) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldName, v)) +} + +// DescriptionEQ applies the EQ predicate on the "description" field. +func DescriptionEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldDescription, v)) +} + +// DescriptionNEQ applies the NEQ predicate on the "description" field. +func DescriptionNEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldDescription, v)) +} + +// DescriptionIn applies the In predicate on the "description" field. +func DescriptionIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldDescription, vs...)) +} + +// DescriptionNotIn applies the NotIn predicate on the "description" field. +func DescriptionNotIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldDescription, vs...)) +} + +// DescriptionGT applies the GT predicate on the "description" field. +func DescriptionGT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldDescription, v)) +} + +// DescriptionGTE applies the GTE predicate on the "description" field. +func DescriptionGTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldDescription, v)) +} + +// DescriptionLT applies the LT predicate on the "description" field. +func DescriptionLT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldDescription, v)) +} + +// DescriptionLTE applies the LTE predicate on the "description" field. +func DescriptionLTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldDescription, v)) +} + +// DescriptionContains applies the Contains predicate on the "description" field. +func DescriptionContains(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContains(FieldDescription, v)) +} + +// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field. +func DescriptionHasPrefix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldDescription, v)) +} + +// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field. +func DescriptionHasSuffix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldDescription, v)) +} + +// DescriptionEqualFold applies the EqualFold predicate on the "description" field. +func DescriptionEqualFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldDescription, v)) +} + +// DescriptionContainsFold applies the ContainsFold predicate on the "description" field. +func DescriptionContainsFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldDescription, v)) +} + +// PriceEQ applies the EQ predicate on the "price" field. +func PriceEQ(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldPrice, v)) +} + +// PriceNEQ applies the NEQ predicate on the "price" field. +func PriceNEQ(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldPrice, v)) +} + +// PriceIn applies the In predicate on the "price" field. +func PriceIn(vs ...float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldPrice, vs...)) +} + +// PriceNotIn applies the NotIn predicate on the "price" field. +func PriceNotIn(vs ...float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldPrice, vs...)) +} + +// PriceGT applies the GT predicate on the "price" field. +func PriceGT(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldPrice, v)) +} + +// PriceGTE applies the GTE predicate on the "price" field. +func PriceGTE(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldPrice, v)) +} + +// PriceLT applies the LT predicate on the "price" field. +func PriceLT(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldPrice, v)) +} + +// PriceLTE applies the LTE predicate on the "price" field. +func PriceLTE(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldPrice, v)) +} + +// OriginalPriceEQ applies the EQ predicate on the "original_price" field. +func OriginalPriceEQ(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldOriginalPrice, v)) +} + +// OriginalPriceNEQ applies the NEQ predicate on the "original_price" field. +func OriginalPriceNEQ(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldOriginalPrice, v)) +} + +// OriginalPriceIn applies the In predicate on the "original_price" field. +func OriginalPriceIn(vs ...float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldOriginalPrice, vs...)) +} + +// OriginalPriceNotIn applies the NotIn predicate on the "original_price" field. +func OriginalPriceNotIn(vs ...float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldOriginalPrice, vs...)) +} + +// OriginalPriceGT applies the GT predicate on the "original_price" field. +func OriginalPriceGT(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldOriginalPrice, v)) +} + +// OriginalPriceGTE applies the GTE predicate on the "original_price" field. +func OriginalPriceGTE(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldOriginalPrice, v)) +} + +// OriginalPriceLT applies the LT predicate on the "original_price" field. +func OriginalPriceLT(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldOriginalPrice, v)) +} + +// OriginalPriceLTE applies the LTE predicate on the "original_price" field. +func OriginalPriceLTE(v float64) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldOriginalPrice, v)) +} + +// OriginalPriceIsNil applies the IsNil predicate on the "original_price" field. +func OriginalPriceIsNil() predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIsNull(FieldOriginalPrice)) +} + +// OriginalPriceNotNil applies the NotNil predicate on the "original_price" field. +func OriginalPriceNotNil() predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotNull(FieldOriginalPrice)) +} + +// ValidityDaysEQ applies the EQ predicate on the "validity_days" field. +func ValidityDaysEQ(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityDays, v)) +} + +// ValidityDaysNEQ applies the NEQ predicate on the "validity_days" field. +func ValidityDaysNEQ(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldValidityDays, v)) +} + +// ValidityDaysIn applies the In predicate on the "validity_days" field. +func ValidityDaysIn(vs ...int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldValidityDays, vs...)) +} + +// ValidityDaysNotIn applies the NotIn predicate on the "validity_days" field. +func ValidityDaysNotIn(vs ...int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldValidityDays, vs...)) +} + +// ValidityDaysGT applies the GT predicate on the "validity_days" field. +func ValidityDaysGT(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldValidityDays, v)) +} + +// ValidityDaysGTE applies the GTE predicate on the "validity_days" field. +func ValidityDaysGTE(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldValidityDays, v)) +} + +// ValidityDaysLT applies the LT predicate on the "validity_days" field. +func ValidityDaysLT(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldValidityDays, v)) +} + +// ValidityDaysLTE applies the LTE predicate on the "validity_days" field. +func ValidityDaysLTE(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldValidityDays, v)) +} + +// ValidityUnitEQ applies the EQ predicate on the "validity_unit" field. +func ValidityUnitEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityUnit, v)) +} + +// ValidityUnitNEQ applies the NEQ predicate on the "validity_unit" field. +func ValidityUnitNEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldValidityUnit, v)) +} + +// ValidityUnitIn applies the In predicate on the "validity_unit" field. +func ValidityUnitIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldValidityUnit, vs...)) +} + +// ValidityUnitNotIn applies the NotIn predicate on the "validity_unit" field. +func ValidityUnitNotIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldValidityUnit, vs...)) +} + +// ValidityUnitGT applies the GT predicate on the "validity_unit" field. +func ValidityUnitGT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldValidityUnit, v)) +} + +// ValidityUnitGTE applies the GTE predicate on the "validity_unit" field. +func ValidityUnitGTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldValidityUnit, v)) +} + +// ValidityUnitLT applies the LT predicate on the "validity_unit" field. +func ValidityUnitLT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldValidityUnit, v)) +} + +// ValidityUnitLTE applies the LTE predicate on the "validity_unit" field. +func ValidityUnitLTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldValidityUnit, v)) +} + +// ValidityUnitContains applies the Contains predicate on the "validity_unit" field. +func ValidityUnitContains(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContains(FieldValidityUnit, v)) +} + +// ValidityUnitHasPrefix applies the HasPrefix predicate on the "validity_unit" field. +func ValidityUnitHasPrefix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldValidityUnit, v)) +} + +// ValidityUnitHasSuffix applies the HasSuffix predicate on the "validity_unit" field. +func ValidityUnitHasSuffix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldValidityUnit, v)) +} + +// ValidityUnitEqualFold applies the EqualFold predicate on the "validity_unit" field. +func ValidityUnitEqualFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldValidityUnit, v)) +} + +// ValidityUnitContainsFold applies the ContainsFold predicate on the "validity_unit" field. +func ValidityUnitContainsFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldValidityUnit, v)) +} + +// FeaturesEQ applies the EQ predicate on the "features" field. +func FeaturesEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldFeatures, v)) +} + +// FeaturesNEQ applies the NEQ predicate on the "features" field. +func FeaturesNEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldFeatures, v)) +} + +// FeaturesIn applies the In predicate on the "features" field. +func FeaturesIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldFeatures, vs...)) +} + +// FeaturesNotIn applies the NotIn predicate on the "features" field. +func FeaturesNotIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldFeatures, vs...)) +} + +// FeaturesGT applies the GT predicate on the "features" field. +func FeaturesGT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldFeatures, v)) +} + +// FeaturesGTE applies the GTE predicate on the "features" field. +func FeaturesGTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldFeatures, v)) +} + +// FeaturesLT applies the LT predicate on the "features" field. +func FeaturesLT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldFeatures, v)) +} + +// FeaturesLTE applies the LTE predicate on the "features" field. +func FeaturesLTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldFeatures, v)) +} + +// FeaturesContains applies the Contains predicate on the "features" field. +func FeaturesContains(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContains(FieldFeatures, v)) +} + +// FeaturesHasPrefix applies the HasPrefix predicate on the "features" field. +func FeaturesHasPrefix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldFeatures, v)) +} + +// FeaturesHasSuffix applies the HasSuffix predicate on the "features" field. +func FeaturesHasSuffix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldFeatures, v)) +} + +// FeaturesEqualFold applies the EqualFold predicate on the "features" field. +func FeaturesEqualFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldFeatures, v)) +} + +// FeaturesContainsFold applies the ContainsFold predicate on the "features" field. +func FeaturesContainsFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldFeatures, v)) +} + +// ProductNameEQ applies the EQ predicate on the "product_name" field. +func ProductNameEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldProductName, v)) +} + +// ProductNameNEQ applies the NEQ predicate on the "product_name" field. +func ProductNameNEQ(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldProductName, v)) +} + +// ProductNameIn applies the In predicate on the "product_name" field. +func ProductNameIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldProductName, vs...)) +} + +// ProductNameNotIn applies the NotIn predicate on the "product_name" field. +func ProductNameNotIn(vs ...string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldProductName, vs...)) +} + +// ProductNameGT applies the GT predicate on the "product_name" field. +func ProductNameGT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldProductName, v)) +} + +// ProductNameGTE applies the GTE predicate on the "product_name" field. +func ProductNameGTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldProductName, v)) +} + +// ProductNameLT applies the LT predicate on the "product_name" field. +func ProductNameLT(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldProductName, v)) +} + +// ProductNameLTE applies the LTE predicate on the "product_name" field. +func ProductNameLTE(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldProductName, v)) +} + +// ProductNameContains applies the Contains predicate on the "product_name" field. +func ProductNameContains(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContains(FieldProductName, v)) +} + +// ProductNameHasPrefix applies the HasPrefix predicate on the "product_name" field. +func ProductNameHasPrefix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldProductName, v)) +} + +// ProductNameHasSuffix applies the HasSuffix predicate on the "product_name" field. +func ProductNameHasSuffix(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldProductName, v)) +} + +// ProductNameEqualFold applies the EqualFold predicate on the "product_name" field. +func ProductNameEqualFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldProductName, v)) +} + +// ProductNameContainsFold applies the ContainsFold predicate on the "product_name" field. +func ProductNameContainsFold(v string) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldProductName, v)) +} + +// ForSaleEQ applies the EQ predicate on the "for_sale" field. +func ForSaleEQ(v bool) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldForSale, v)) +} + +// ForSaleNEQ applies the NEQ predicate on the "for_sale" field. +func ForSaleNEQ(v bool) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldForSale, v)) +} + +// SortOrderEQ applies the EQ predicate on the "sort_order" field. +func SortOrderEQ(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldSortOrder, v)) +} + +// SortOrderNEQ applies the NEQ predicate on the "sort_order" field. +func SortOrderNEQ(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldSortOrder, v)) +} + +// SortOrderIn applies the In predicate on the "sort_order" field. +func SortOrderIn(vs ...int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldSortOrder, vs...)) +} + +// SortOrderNotIn applies the NotIn predicate on the "sort_order" field. +func SortOrderNotIn(vs ...int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldSortOrder, vs...)) +} + +// SortOrderGT applies the GT predicate on the "sort_order" field. +func SortOrderGT(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldSortOrder, v)) +} + +// SortOrderGTE applies the GTE predicate on the "sort_order" field. +func SortOrderGTE(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldSortOrder, v)) +} + +// SortOrderLT applies the LT predicate on the "sort_order" field. +func SortOrderLT(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldSortOrder, v)) +} + +// SortOrderLTE applies the LTE predicate on the "sort_order" field. +func SortOrderLTE(v int) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldSortOrder, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.SubscriptionPlan) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.SubscriptionPlan) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.SubscriptionPlan) predicate.SubscriptionPlan { + return predicate.SubscriptionPlan(sql.NotPredicates(p)) +} diff --git a/backend/ent/subscriptionplan_create.go b/backend/ent/subscriptionplan_create.go new file mode 100644 index 0000000000000000000000000000000000000000..9109db3a01ea75b6a67aa349c46555164305277b --- /dev/null +++ b/backend/ent/subscriptionplan_create.go @@ -0,0 +1,1317 @@ +// 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/subscriptionplan" +) + +// SubscriptionPlanCreate is the builder for creating a SubscriptionPlan entity. +type SubscriptionPlanCreate struct { + config + mutation *SubscriptionPlanMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetGroupID sets the "group_id" field. +func (_c *SubscriptionPlanCreate) SetGroupID(v int64) *SubscriptionPlanCreate { + _c.mutation.SetGroupID(v) + return _c +} + +// SetName sets the "name" field. +func (_c *SubscriptionPlanCreate) SetName(v string) *SubscriptionPlanCreate { + _c.mutation.SetName(v) + return _c +} + +// SetDescription sets the "description" field. +func (_c *SubscriptionPlanCreate) SetDescription(v string) *SubscriptionPlanCreate { + _c.mutation.SetDescription(v) + return _c +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableDescription(v *string) *SubscriptionPlanCreate { + if v != nil { + _c.SetDescription(*v) + } + return _c +} + +// SetPrice sets the "price" field. +func (_c *SubscriptionPlanCreate) SetPrice(v float64) *SubscriptionPlanCreate { + _c.mutation.SetPrice(v) + return _c +} + +// SetOriginalPrice sets the "original_price" field. +func (_c *SubscriptionPlanCreate) SetOriginalPrice(v float64) *SubscriptionPlanCreate { + _c.mutation.SetOriginalPrice(v) + return _c +} + +// SetNillableOriginalPrice sets the "original_price" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableOriginalPrice(v *float64) *SubscriptionPlanCreate { + if v != nil { + _c.SetOriginalPrice(*v) + } + return _c +} + +// SetValidityDays sets the "validity_days" field. +func (_c *SubscriptionPlanCreate) SetValidityDays(v int) *SubscriptionPlanCreate { + _c.mutation.SetValidityDays(v) + return _c +} + +// SetNillableValidityDays sets the "validity_days" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableValidityDays(v *int) *SubscriptionPlanCreate { + if v != nil { + _c.SetValidityDays(*v) + } + return _c +} + +// SetValidityUnit sets the "validity_unit" field. +func (_c *SubscriptionPlanCreate) SetValidityUnit(v string) *SubscriptionPlanCreate { + _c.mutation.SetValidityUnit(v) + return _c +} + +// SetNillableValidityUnit sets the "validity_unit" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableValidityUnit(v *string) *SubscriptionPlanCreate { + if v != nil { + _c.SetValidityUnit(*v) + } + return _c +} + +// SetFeatures sets the "features" field. +func (_c *SubscriptionPlanCreate) SetFeatures(v string) *SubscriptionPlanCreate { + _c.mutation.SetFeatures(v) + return _c +} + +// SetNillableFeatures sets the "features" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableFeatures(v *string) *SubscriptionPlanCreate { + if v != nil { + _c.SetFeatures(*v) + } + return _c +} + +// SetProductName sets the "product_name" field. +func (_c *SubscriptionPlanCreate) SetProductName(v string) *SubscriptionPlanCreate { + _c.mutation.SetProductName(v) + return _c +} + +// SetNillableProductName sets the "product_name" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableProductName(v *string) *SubscriptionPlanCreate { + if v != nil { + _c.SetProductName(*v) + } + return _c +} + +// SetForSale sets the "for_sale" field. +func (_c *SubscriptionPlanCreate) SetForSale(v bool) *SubscriptionPlanCreate { + _c.mutation.SetForSale(v) + return _c +} + +// SetNillableForSale sets the "for_sale" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableForSale(v *bool) *SubscriptionPlanCreate { + if v != nil { + _c.SetForSale(*v) + } + return _c +} + +// SetSortOrder sets the "sort_order" field. +func (_c *SubscriptionPlanCreate) SetSortOrder(v int) *SubscriptionPlanCreate { + _c.mutation.SetSortOrder(v) + return _c +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableSortOrder(v *int) *SubscriptionPlanCreate { + if v != nil { + _c.SetSortOrder(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *SubscriptionPlanCreate) SetCreatedAt(v time.Time) *SubscriptionPlanCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableCreatedAt(v *time.Time) *SubscriptionPlanCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *SubscriptionPlanCreate) SetUpdatedAt(v time.Time) *SubscriptionPlanCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *SubscriptionPlanCreate) SetNillableUpdatedAt(v *time.Time) *SubscriptionPlanCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// Mutation returns the SubscriptionPlanMutation object of the builder. +func (_c *SubscriptionPlanCreate) Mutation() *SubscriptionPlanMutation { + return _c.mutation +} + +// Save creates the SubscriptionPlan in the database. +func (_c *SubscriptionPlanCreate) Save(ctx context.Context) (*SubscriptionPlan, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *SubscriptionPlanCreate) SaveX(ctx context.Context) *SubscriptionPlan { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SubscriptionPlanCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SubscriptionPlanCreate) 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 *SubscriptionPlanCreate) defaults() { + if _, ok := _c.mutation.Description(); !ok { + v := subscriptionplan.DefaultDescription + _c.mutation.SetDescription(v) + } + if _, ok := _c.mutation.ValidityDays(); !ok { + v := subscriptionplan.DefaultValidityDays + _c.mutation.SetValidityDays(v) + } + if _, ok := _c.mutation.ValidityUnit(); !ok { + v := subscriptionplan.DefaultValidityUnit + _c.mutation.SetValidityUnit(v) + } + if _, ok := _c.mutation.Features(); !ok { + v := subscriptionplan.DefaultFeatures + _c.mutation.SetFeatures(v) + } + if _, ok := _c.mutation.ProductName(); !ok { + v := subscriptionplan.DefaultProductName + _c.mutation.SetProductName(v) + } + if _, ok := _c.mutation.ForSale(); !ok { + v := subscriptionplan.DefaultForSale + _c.mutation.SetForSale(v) + } + if _, ok := _c.mutation.SortOrder(); !ok { + v := subscriptionplan.DefaultSortOrder + _c.mutation.SetSortOrder(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := subscriptionplan.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := subscriptionplan.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *SubscriptionPlanCreate) check() error { + if _, ok := _c.mutation.GroupID(); !ok { + return &ValidationError{Name: "group_id", err: errors.New(`ent: missing required field "SubscriptionPlan.group_id"`)} + } + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "SubscriptionPlan.name"`)} + } + if v, ok := _c.mutation.Name(); ok { + if err := subscriptionplan.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)} + } + } + if _, ok := _c.mutation.Description(); !ok { + return &ValidationError{Name: "description", err: errors.New(`ent: missing required field "SubscriptionPlan.description"`)} + } + if _, ok := _c.mutation.Price(); !ok { + return &ValidationError{Name: "price", err: errors.New(`ent: missing required field "SubscriptionPlan.price"`)} + } + if _, ok := _c.mutation.ValidityDays(); !ok { + return &ValidationError{Name: "validity_days", err: errors.New(`ent: missing required field "SubscriptionPlan.validity_days"`)} + } + if _, ok := _c.mutation.ValidityUnit(); !ok { + return &ValidationError{Name: "validity_unit", err: errors.New(`ent: missing required field "SubscriptionPlan.validity_unit"`)} + } + if v, ok := _c.mutation.ValidityUnit(); ok { + if err := subscriptionplan.ValidityUnitValidator(v); err != nil { + return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)} + } + } + if _, ok := _c.mutation.Features(); !ok { + return &ValidationError{Name: "features", err: errors.New(`ent: missing required field "SubscriptionPlan.features"`)} + } + if _, ok := _c.mutation.ProductName(); !ok { + return &ValidationError{Name: "product_name", err: errors.New(`ent: missing required field "SubscriptionPlan.product_name"`)} + } + if v, ok := _c.mutation.ProductName(); ok { + if err := subscriptionplan.ProductNameValidator(v); err != nil { + return &ValidationError{Name: "product_name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.product_name": %w`, err)} + } + } + if _, ok := _c.mutation.ForSale(); !ok { + return &ValidationError{Name: "for_sale", err: errors.New(`ent: missing required field "SubscriptionPlan.for_sale"`)} + } + if _, ok := _c.mutation.SortOrder(); !ok { + return &ValidationError{Name: "sort_order", err: errors.New(`ent: missing required field "SubscriptionPlan.sort_order"`)} + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "SubscriptionPlan.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "SubscriptionPlan.updated_at"`)} + } + return nil +} + +func (_c *SubscriptionPlanCreate) sqlSave(ctx context.Context) (*SubscriptionPlan, 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 *SubscriptionPlanCreate) createSpec() (*SubscriptionPlan, *sqlgraph.CreateSpec) { + var ( + _node = &SubscriptionPlan{config: _c.config} + _spec = sqlgraph.NewCreateSpec(subscriptionplan.Table, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if value, ok := _c.mutation.GroupID(); ok { + _spec.SetField(subscriptionplan.FieldGroupID, field.TypeInt64, value) + _node.GroupID = value + } + if value, ok := _c.mutation.Name(); ok { + _spec.SetField(subscriptionplan.FieldName, field.TypeString, value) + _node.Name = value + } + if value, ok := _c.mutation.Description(); ok { + _spec.SetField(subscriptionplan.FieldDescription, field.TypeString, value) + _node.Description = value + } + if value, ok := _c.mutation.Price(); ok { + _spec.SetField(subscriptionplan.FieldPrice, field.TypeFloat64, value) + _node.Price = value + } + if value, ok := _c.mutation.OriginalPrice(); ok { + _spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value) + _node.OriginalPrice = &value + } + if value, ok := _c.mutation.ValidityDays(); ok { + _spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value) + _node.ValidityDays = value + } + if value, ok := _c.mutation.ValidityUnit(); ok { + _spec.SetField(subscriptionplan.FieldValidityUnit, field.TypeString, value) + _node.ValidityUnit = value + } + if value, ok := _c.mutation.Features(); ok { + _spec.SetField(subscriptionplan.FieldFeatures, field.TypeString, value) + _node.Features = value + } + if value, ok := _c.mutation.ProductName(); ok { + _spec.SetField(subscriptionplan.FieldProductName, field.TypeString, value) + _node.ProductName = value + } + if value, ok := _c.mutation.ForSale(); ok { + _spec.SetField(subscriptionplan.FieldForSale, field.TypeBool, value) + _node.ForSale = value + } + if value, ok := _c.mutation.SortOrder(); ok { + _spec.SetField(subscriptionplan.FieldSortOrder, field.TypeInt, value) + _node.SortOrder = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(subscriptionplan.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(subscriptionplan.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.SubscriptionPlan.Create(). +// SetGroupID(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.SubscriptionPlanUpsert) { +// SetGroupID(v+v). +// }). +// Exec(ctx) +func (_c *SubscriptionPlanCreate) OnConflict(opts ...sql.ConflictOption) *SubscriptionPlanUpsertOne { + _c.conflict = opts + return &SubscriptionPlanUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.SubscriptionPlan.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *SubscriptionPlanCreate) OnConflictColumns(columns ...string) *SubscriptionPlanUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &SubscriptionPlanUpsertOne{ + create: _c, + } +} + +type ( + // SubscriptionPlanUpsertOne is the builder for "upsert"-ing + // one SubscriptionPlan node. + SubscriptionPlanUpsertOne struct { + create *SubscriptionPlanCreate + } + + // SubscriptionPlanUpsert is the "OnConflict" setter. + SubscriptionPlanUpsert struct { + *sql.UpdateSet + } +) + +// SetGroupID sets the "group_id" field. +func (u *SubscriptionPlanUpsert) SetGroupID(v int64) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldGroupID, v) + return u +} + +// UpdateGroupID sets the "group_id" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateGroupID() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldGroupID) + return u +} + +// AddGroupID adds v to the "group_id" field. +func (u *SubscriptionPlanUpsert) AddGroupID(v int64) *SubscriptionPlanUpsert { + u.Add(subscriptionplan.FieldGroupID, v) + return u +} + +// SetName sets the "name" field. +func (u *SubscriptionPlanUpsert) SetName(v string) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldName, v) + return u +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateName() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldName) + return u +} + +// SetDescription sets the "description" field. +func (u *SubscriptionPlanUpsert) SetDescription(v string) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldDescription, v) + return u +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateDescription() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldDescription) + return u +} + +// SetPrice sets the "price" field. +func (u *SubscriptionPlanUpsert) SetPrice(v float64) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldPrice, v) + return u +} + +// UpdatePrice sets the "price" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdatePrice() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldPrice) + return u +} + +// AddPrice adds v to the "price" field. +func (u *SubscriptionPlanUpsert) AddPrice(v float64) *SubscriptionPlanUpsert { + u.Add(subscriptionplan.FieldPrice, v) + return u +} + +// SetOriginalPrice sets the "original_price" field. +func (u *SubscriptionPlanUpsert) SetOriginalPrice(v float64) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldOriginalPrice, v) + return u +} + +// UpdateOriginalPrice sets the "original_price" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateOriginalPrice() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldOriginalPrice) + return u +} + +// AddOriginalPrice adds v to the "original_price" field. +func (u *SubscriptionPlanUpsert) AddOriginalPrice(v float64) *SubscriptionPlanUpsert { + u.Add(subscriptionplan.FieldOriginalPrice, v) + return u +} + +// ClearOriginalPrice clears the value of the "original_price" field. +func (u *SubscriptionPlanUpsert) ClearOriginalPrice() *SubscriptionPlanUpsert { + u.SetNull(subscriptionplan.FieldOriginalPrice) + return u +} + +// SetValidityDays sets the "validity_days" field. +func (u *SubscriptionPlanUpsert) SetValidityDays(v int) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldValidityDays, v) + return u +} + +// UpdateValidityDays sets the "validity_days" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateValidityDays() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldValidityDays) + return u +} + +// AddValidityDays adds v to the "validity_days" field. +func (u *SubscriptionPlanUpsert) AddValidityDays(v int) *SubscriptionPlanUpsert { + u.Add(subscriptionplan.FieldValidityDays, v) + return u +} + +// SetValidityUnit sets the "validity_unit" field. +func (u *SubscriptionPlanUpsert) SetValidityUnit(v string) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldValidityUnit, v) + return u +} + +// UpdateValidityUnit sets the "validity_unit" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateValidityUnit() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldValidityUnit) + return u +} + +// SetFeatures sets the "features" field. +func (u *SubscriptionPlanUpsert) SetFeatures(v string) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldFeatures, v) + return u +} + +// UpdateFeatures sets the "features" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateFeatures() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldFeatures) + return u +} + +// SetProductName sets the "product_name" field. +func (u *SubscriptionPlanUpsert) SetProductName(v string) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldProductName, v) + return u +} + +// UpdateProductName sets the "product_name" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateProductName() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldProductName) + return u +} + +// SetForSale sets the "for_sale" field. +func (u *SubscriptionPlanUpsert) SetForSale(v bool) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldForSale, v) + return u +} + +// UpdateForSale sets the "for_sale" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateForSale() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldForSale) + return u +} + +// SetSortOrder sets the "sort_order" field. +func (u *SubscriptionPlanUpsert) SetSortOrder(v int) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldSortOrder, v) + return u +} + +// UpdateSortOrder sets the "sort_order" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateSortOrder() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.FieldSortOrder) + return u +} + +// AddSortOrder adds v to the "sort_order" field. +func (u *SubscriptionPlanUpsert) AddSortOrder(v int) *SubscriptionPlanUpsert { + u.Add(subscriptionplan.FieldSortOrder, v) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *SubscriptionPlanUpsert) SetUpdatedAt(v time.Time) *SubscriptionPlanUpsert { + u.Set(subscriptionplan.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *SubscriptionPlanUpsert) UpdateUpdatedAt() *SubscriptionPlanUpsert { + u.SetExcluded(subscriptionplan.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.SubscriptionPlan.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *SubscriptionPlanUpsertOne) UpdateNewValues() *SubscriptionPlanUpsertOne { + 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(subscriptionplan.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.SubscriptionPlan.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *SubscriptionPlanUpsertOne) Ignore() *SubscriptionPlanUpsertOne { + 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 *SubscriptionPlanUpsertOne) DoNothing() *SubscriptionPlanUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the SubscriptionPlanCreate.OnConflict +// documentation for more info. +func (u *SubscriptionPlanUpsertOne) Update(set func(*SubscriptionPlanUpsert)) *SubscriptionPlanUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&SubscriptionPlanUpsert{UpdateSet: update}) + })) + return u +} + +// SetGroupID sets the "group_id" field. +func (u *SubscriptionPlanUpsertOne) SetGroupID(v int64) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetGroupID(v) + }) +} + +// AddGroupID adds v to the "group_id" field. +func (u *SubscriptionPlanUpsertOne) AddGroupID(v int64) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddGroupID(v) + }) +} + +// UpdateGroupID sets the "group_id" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateGroupID() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateGroupID() + }) +} + +// SetName sets the "name" field. +func (u *SubscriptionPlanUpsertOne) SetName(v string) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateName() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateName() + }) +} + +// SetDescription sets the "description" field. +func (u *SubscriptionPlanUpsertOne) SetDescription(v string) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetDescription(v) + }) +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateDescription() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateDescription() + }) +} + +// SetPrice sets the "price" field. +func (u *SubscriptionPlanUpsertOne) SetPrice(v float64) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetPrice(v) + }) +} + +// AddPrice adds v to the "price" field. +func (u *SubscriptionPlanUpsertOne) AddPrice(v float64) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddPrice(v) + }) +} + +// UpdatePrice sets the "price" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdatePrice() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdatePrice() + }) +} + +// SetOriginalPrice sets the "original_price" field. +func (u *SubscriptionPlanUpsertOne) SetOriginalPrice(v float64) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetOriginalPrice(v) + }) +} + +// AddOriginalPrice adds v to the "original_price" field. +func (u *SubscriptionPlanUpsertOne) AddOriginalPrice(v float64) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddOriginalPrice(v) + }) +} + +// UpdateOriginalPrice sets the "original_price" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateOriginalPrice() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateOriginalPrice() + }) +} + +// ClearOriginalPrice clears the value of the "original_price" field. +func (u *SubscriptionPlanUpsertOne) ClearOriginalPrice() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.ClearOriginalPrice() + }) +} + +// SetValidityDays sets the "validity_days" field. +func (u *SubscriptionPlanUpsertOne) SetValidityDays(v int) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetValidityDays(v) + }) +} + +// AddValidityDays adds v to the "validity_days" field. +func (u *SubscriptionPlanUpsertOne) AddValidityDays(v int) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddValidityDays(v) + }) +} + +// UpdateValidityDays sets the "validity_days" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateValidityDays() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateValidityDays() + }) +} + +// SetValidityUnit sets the "validity_unit" field. +func (u *SubscriptionPlanUpsertOne) SetValidityUnit(v string) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetValidityUnit(v) + }) +} + +// UpdateValidityUnit sets the "validity_unit" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateValidityUnit() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateValidityUnit() + }) +} + +// SetFeatures sets the "features" field. +func (u *SubscriptionPlanUpsertOne) SetFeatures(v string) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetFeatures(v) + }) +} + +// UpdateFeatures sets the "features" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateFeatures() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateFeatures() + }) +} + +// SetProductName sets the "product_name" field. +func (u *SubscriptionPlanUpsertOne) SetProductName(v string) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetProductName(v) + }) +} + +// UpdateProductName sets the "product_name" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateProductName() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateProductName() + }) +} + +// SetForSale sets the "for_sale" field. +func (u *SubscriptionPlanUpsertOne) SetForSale(v bool) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetForSale(v) + }) +} + +// UpdateForSale sets the "for_sale" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateForSale() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateForSale() + }) +} + +// SetSortOrder sets the "sort_order" field. +func (u *SubscriptionPlanUpsertOne) SetSortOrder(v int) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetSortOrder(v) + }) +} + +// AddSortOrder adds v to the "sort_order" field. +func (u *SubscriptionPlanUpsertOne) AddSortOrder(v int) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddSortOrder(v) + }) +} + +// UpdateSortOrder sets the "sort_order" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateSortOrder() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateSortOrder() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *SubscriptionPlanUpsertOne) SetUpdatedAt(v time.Time) *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertOne) UpdateUpdatedAt() *SubscriptionPlanUpsertOne { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *SubscriptionPlanUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for SubscriptionPlanCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *SubscriptionPlanUpsertOne) 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 *SubscriptionPlanUpsertOne) 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 *SubscriptionPlanUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// SubscriptionPlanCreateBulk is the builder for creating many SubscriptionPlan entities in bulk. +type SubscriptionPlanCreateBulk struct { + config + err error + builders []*SubscriptionPlanCreate + conflict []sql.ConflictOption +} + +// Save creates the SubscriptionPlan entities in the database. +func (_c *SubscriptionPlanCreateBulk) Save(ctx context.Context) ([]*SubscriptionPlan, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*SubscriptionPlan, 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.(*SubscriptionPlanMutation) + 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 *SubscriptionPlanCreateBulk) SaveX(ctx context.Context) []*SubscriptionPlan { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SubscriptionPlanCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SubscriptionPlanCreateBulk) 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.SubscriptionPlan.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.SubscriptionPlanUpsert) { +// SetGroupID(v+v). +// }). +// Exec(ctx) +func (_c *SubscriptionPlanCreateBulk) OnConflict(opts ...sql.ConflictOption) *SubscriptionPlanUpsertBulk { + _c.conflict = opts + return &SubscriptionPlanUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.SubscriptionPlan.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *SubscriptionPlanCreateBulk) OnConflictColumns(columns ...string) *SubscriptionPlanUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &SubscriptionPlanUpsertBulk{ + create: _c, + } +} + +// SubscriptionPlanUpsertBulk is the builder for "upsert"-ing +// a bulk of SubscriptionPlan nodes. +type SubscriptionPlanUpsertBulk struct { + create *SubscriptionPlanCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.SubscriptionPlan.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *SubscriptionPlanUpsertBulk) UpdateNewValues() *SubscriptionPlanUpsertBulk { + 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(subscriptionplan.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.SubscriptionPlan.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *SubscriptionPlanUpsertBulk) Ignore() *SubscriptionPlanUpsertBulk { + 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 *SubscriptionPlanUpsertBulk) DoNothing() *SubscriptionPlanUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the SubscriptionPlanCreateBulk.OnConflict +// documentation for more info. +func (u *SubscriptionPlanUpsertBulk) Update(set func(*SubscriptionPlanUpsert)) *SubscriptionPlanUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&SubscriptionPlanUpsert{UpdateSet: update}) + })) + return u +} + +// SetGroupID sets the "group_id" field. +func (u *SubscriptionPlanUpsertBulk) SetGroupID(v int64) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetGroupID(v) + }) +} + +// AddGroupID adds v to the "group_id" field. +func (u *SubscriptionPlanUpsertBulk) AddGroupID(v int64) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddGroupID(v) + }) +} + +// UpdateGroupID sets the "group_id" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateGroupID() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateGroupID() + }) +} + +// SetName sets the "name" field. +func (u *SubscriptionPlanUpsertBulk) SetName(v string) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetName(v) + }) +} + +// UpdateName sets the "name" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateName() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateName() + }) +} + +// SetDescription sets the "description" field. +func (u *SubscriptionPlanUpsertBulk) SetDescription(v string) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetDescription(v) + }) +} + +// UpdateDescription sets the "description" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateDescription() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateDescription() + }) +} + +// SetPrice sets the "price" field. +func (u *SubscriptionPlanUpsertBulk) SetPrice(v float64) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetPrice(v) + }) +} + +// AddPrice adds v to the "price" field. +func (u *SubscriptionPlanUpsertBulk) AddPrice(v float64) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddPrice(v) + }) +} + +// UpdatePrice sets the "price" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdatePrice() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdatePrice() + }) +} + +// SetOriginalPrice sets the "original_price" field. +func (u *SubscriptionPlanUpsertBulk) SetOriginalPrice(v float64) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetOriginalPrice(v) + }) +} + +// AddOriginalPrice adds v to the "original_price" field. +func (u *SubscriptionPlanUpsertBulk) AddOriginalPrice(v float64) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddOriginalPrice(v) + }) +} + +// UpdateOriginalPrice sets the "original_price" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateOriginalPrice() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateOriginalPrice() + }) +} + +// ClearOriginalPrice clears the value of the "original_price" field. +func (u *SubscriptionPlanUpsertBulk) ClearOriginalPrice() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.ClearOriginalPrice() + }) +} + +// SetValidityDays sets the "validity_days" field. +func (u *SubscriptionPlanUpsertBulk) SetValidityDays(v int) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetValidityDays(v) + }) +} + +// AddValidityDays adds v to the "validity_days" field. +func (u *SubscriptionPlanUpsertBulk) AddValidityDays(v int) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddValidityDays(v) + }) +} + +// UpdateValidityDays sets the "validity_days" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateValidityDays() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateValidityDays() + }) +} + +// SetValidityUnit sets the "validity_unit" field. +func (u *SubscriptionPlanUpsertBulk) SetValidityUnit(v string) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetValidityUnit(v) + }) +} + +// UpdateValidityUnit sets the "validity_unit" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateValidityUnit() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateValidityUnit() + }) +} + +// SetFeatures sets the "features" field. +func (u *SubscriptionPlanUpsertBulk) SetFeatures(v string) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetFeatures(v) + }) +} + +// UpdateFeatures sets the "features" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateFeatures() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateFeatures() + }) +} + +// SetProductName sets the "product_name" field. +func (u *SubscriptionPlanUpsertBulk) SetProductName(v string) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetProductName(v) + }) +} + +// UpdateProductName sets the "product_name" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateProductName() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateProductName() + }) +} + +// SetForSale sets the "for_sale" field. +func (u *SubscriptionPlanUpsertBulk) SetForSale(v bool) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetForSale(v) + }) +} + +// UpdateForSale sets the "for_sale" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateForSale() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateForSale() + }) +} + +// SetSortOrder sets the "sort_order" field. +func (u *SubscriptionPlanUpsertBulk) SetSortOrder(v int) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetSortOrder(v) + }) +} + +// AddSortOrder adds v to the "sort_order" field. +func (u *SubscriptionPlanUpsertBulk) AddSortOrder(v int) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.AddSortOrder(v) + }) +} + +// UpdateSortOrder sets the "sort_order" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateSortOrder() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateSortOrder() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *SubscriptionPlanUpsertBulk) SetUpdatedAt(v time.Time) *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *SubscriptionPlanUpsertBulk) UpdateUpdatedAt() *SubscriptionPlanUpsertBulk { + return u.Update(func(s *SubscriptionPlanUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *SubscriptionPlanUpsertBulk) 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 SubscriptionPlanCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for SubscriptionPlanCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *SubscriptionPlanUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/subscriptionplan_delete.go b/backend/ent/subscriptionplan_delete.go new file mode 100644 index 0000000000000000000000000000000000000000..90c71239e2aebf92198bfd60f213dc9833910892 --- /dev/null +++ b/backend/ent/subscriptionplan_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/subscriptionplan" +) + +// SubscriptionPlanDelete is the builder for deleting a SubscriptionPlan entity. +type SubscriptionPlanDelete struct { + config + hooks []Hook + mutation *SubscriptionPlanMutation +} + +// Where appends a list predicates to the SubscriptionPlanDelete builder. +func (_d *SubscriptionPlanDelete) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *SubscriptionPlanDelete) 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 *SubscriptionPlanDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *SubscriptionPlanDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(subscriptionplan.Table, sqlgraph.NewFieldSpec(subscriptionplan.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 +} + +// SubscriptionPlanDeleteOne is the builder for deleting a single SubscriptionPlan entity. +type SubscriptionPlanDeleteOne struct { + _d *SubscriptionPlanDelete +} + +// Where appends a list predicates to the SubscriptionPlanDelete builder. +func (_d *SubscriptionPlanDeleteOne) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *SubscriptionPlanDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{subscriptionplan.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SubscriptionPlanDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/subscriptionplan_query.go b/backend/ent/subscriptionplan_query.go new file mode 100644 index 0000000000000000000000000000000000000000..6c301dcd2948d120ac91e7a9e1a697c432401ec1 --- /dev/null +++ b/backend/ent/subscriptionplan_query.go @@ -0,0 +1,564 @@ +// 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/subscriptionplan" +) + +// SubscriptionPlanQuery is the builder for querying SubscriptionPlan entities. +type SubscriptionPlanQuery struct { + config + ctx *QueryContext + order []subscriptionplan.OrderOption + inters []Interceptor + predicates []predicate.SubscriptionPlan + 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 SubscriptionPlanQuery builder. +func (_q *SubscriptionPlanQuery) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *SubscriptionPlanQuery) Limit(limit int) *SubscriptionPlanQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *SubscriptionPlanQuery) Offset(offset int) *SubscriptionPlanQuery { + _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 *SubscriptionPlanQuery) Unique(unique bool) *SubscriptionPlanQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *SubscriptionPlanQuery) Order(o ...subscriptionplan.OrderOption) *SubscriptionPlanQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first SubscriptionPlan entity from the query. +// Returns a *NotFoundError when no SubscriptionPlan was found. +func (_q *SubscriptionPlanQuery) First(ctx context.Context) (*SubscriptionPlan, 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{subscriptionplan.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) FirstX(ctx context.Context) *SubscriptionPlan { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first SubscriptionPlan ID from the query. +// Returns a *NotFoundError when no SubscriptionPlan ID was found. +func (_q *SubscriptionPlanQuery) 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{subscriptionplan.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single SubscriptionPlan entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one SubscriptionPlan entity is found. +// Returns a *NotFoundError when no SubscriptionPlan entities are found. +func (_q *SubscriptionPlanQuery) Only(ctx context.Context) (*SubscriptionPlan, 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{subscriptionplan.Label} + default: + return nil, &NotSingularError{subscriptionplan.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) OnlyX(ctx context.Context) *SubscriptionPlan { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only SubscriptionPlan ID in the query. +// Returns a *NotSingularError when more than one SubscriptionPlan ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *SubscriptionPlanQuery) 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{subscriptionplan.Label} + default: + err = &NotSingularError{subscriptionplan.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) 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 SubscriptionPlans. +func (_q *SubscriptionPlanQuery) All(ctx context.Context) ([]*SubscriptionPlan, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*SubscriptionPlan, *SubscriptionPlanQuery]() + return withInterceptors[[]*SubscriptionPlan](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) AllX(ctx context.Context) []*SubscriptionPlan { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of SubscriptionPlan IDs. +func (_q *SubscriptionPlanQuery) 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(subscriptionplan.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) 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 *SubscriptionPlanQuery) 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[*SubscriptionPlanQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *SubscriptionPlanQuery) 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 *SubscriptionPlanQuery) 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 *SubscriptionPlanQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SubscriptionPlanQuery 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 *SubscriptionPlanQuery) Clone() *SubscriptionPlanQuery { + if _q == nil { + return nil + } + return &SubscriptionPlanQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]subscriptionplan.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.SubscriptionPlan{}, _q.predicates...), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// 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 { +// GroupID int64 `json:"group_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.SubscriptionPlan.Query(). +// GroupBy(subscriptionplan.FieldGroupID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *SubscriptionPlanQuery) GroupBy(field string, fields ...string) *SubscriptionPlanGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &SubscriptionPlanGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = subscriptionplan.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 { +// GroupID int64 `json:"group_id,omitempty"` +// } +// +// client.SubscriptionPlan.Query(). +// Select(subscriptionplan.FieldGroupID). +// Scan(ctx, &v) +func (_q *SubscriptionPlanQuery) Select(fields ...string) *SubscriptionPlanSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &SubscriptionPlanSelect{SubscriptionPlanQuery: _q} + sbuild.label = subscriptionplan.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a SubscriptionPlanSelect configured with the given aggregations. +func (_q *SubscriptionPlanQuery) Aggregate(fns ...AggregateFunc) *SubscriptionPlanSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *SubscriptionPlanQuery) 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 !subscriptionplan.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 *SubscriptionPlanQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SubscriptionPlan, error) { + var ( + nodes = []*SubscriptionPlan{} + _spec = _q.querySpec() + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*SubscriptionPlan).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &SubscriptionPlan{config: _q.config} + 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) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + return nodes, nil +} + +func (_q *SubscriptionPlanQuery) 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 *SubscriptionPlanQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(subscriptionplan.Table, subscriptionplan.Columns, sqlgraph.NewFieldSpec(subscriptionplan.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, subscriptionplan.FieldID) + for i := range fields { + if fields[i] != subscriptionplan.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 *SubscriptionPlanQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(subscriptionplan.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = subscriptionplan.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 *SubscriptionPlanQuery) ForUpdate(opts ...sql.LockOption) *SubscriptionPlanQuery { + 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 *SubscriptionPlanQuery) ForShare(opts ...sql.LockOption) *SubscriptionPlanQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// SubscriptionPlanGroupBy is the group-by builder for SubscriptionPlan entities. +type SubscriptionPlanGroupBy struct { + selector + build *SubscriptionPlanQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *SubscriptionPlanGroupBy) Aggregate(fns ...AggregateFunc) *SubscriptionPlanGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *SubscriptionPlanGroupBy) 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[*SubscriptionPlanQuery, *SubscriptionPlanGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *SubscriptionPlanGroupBy) sqlScan(ctx context.Context, root *SubscriptionPlanQuery, 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) +} + +// SubscriptionPlanSelect is the builder for selecting fields of SubscriptionPlan entities. +type SubscriptionPlanSelect struct { + *SubscriptionPlanQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *SubscriptionPlanSelect) Aggregate(fns ...AggregateFunc) *SubscriptionPlanSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *SubscriptionPlanSelect) 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[*SubscriptionPlanQuery, *SubscriptionPlanSelect](ctx, _s.SubscriptionPlanQuery, _s, _s.inters, v) +} + +func (_s *SubscriptionPlanSelect) sqlScan(ctx context.Context, root *SubscriptionPlanQuery, 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/subscriptionplan_update.go b/backend/ent/subscriptionplan_update.go new file mode 100644 index 0000000000000000000000000000000000000000..c9225d0f7eae4c52269ba2072b23970d4dcbf047 --- /dev/null +++ b/backend/ent/subscriptionplan_update.go @@ -0,0 +1,750 @@ +// 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/subscriptionplan" +) + +// SubscriptionPlanUpdate is the builder for updating SubscriptionPlan entities. +type SubscriptionPlanUpdate struct { + config + hooks []Hook + mutation *SubscriptionPlanMutation +} + +// Where appends a list predicates to the SubscriptionPlanUpdate builder. +func (_u *SubscriptionPlanUpdate) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetGroupID sets the "group_id" field. +func (_u *SubscriptionPlanUpdate) SetGroupID(v int64) *SubscriptionPlanUpdate { + _u.mutation.ResetGroupID() + _u.mutation.SetGroupID(v) + return _u +} + +// SetNillableGroupID sets the "group_id" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableGroupID(v *int64) *SubscriptionPlanUpdate { + if v != nil { + _u.SetGroupID(*v) + } + return _u +} + +// AddGroupID adds value to the "group_id" field. +func (_u *SubscriptionPlanUpdate) AddGroupID(v int64) *SubscriptionPlanUpdate { + _u.mutation.AddGroupID(v) + return _u +} + +// SetName sets the "name" field. +func (_u *SubscriptionPlanUpdate) SetName(v string) *SubscriptionPlanUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableName(v *string) *SubscriptionPlanUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetDescription sets the "description" field. +func (_u *SubscriptionPlanUpdate) SetDescription(v string) *SubscriptionPlanUpdate { + _u.mutation.SetDescription(v) + return _u +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableDescription(v *string) *SubscriptionPlanUpdate { + if v != nil { + _u.SetDescription(*v) + } + return _u +} + +// SetPrice sets the "price" field. +func (_u *SubscriptionPlanUpdate) SetPrice(v float64) *SubscriptionPlanUpdate { + _u.mutation.ResetPrice() + _u.mutation.SetPrice(v) + return _u +} + +// SetNillablePrice sets the "price" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillablePrice(v *float64) *SubscriptionPlanUpdate { + if v != nil { + _u.SetPrice(*v) + } + return _u +} + +// AddPrice adds value to the "price" field. +func (_u *SubscriptionPlanUpdate) AddPrice(v float64) *SubscriptionPlanUpdate { + _u.mutation.AddPrice(v) + return _u +} + +// SetOriginalPrice sets the "original_price" field. +func (_u *SubscriptionPlanUpdate) SetOriginalPrice(v float64) *SubscriptionPlanUpdate { + _u.mutation.ResetOriginalPrice() + _u.mutation.SetOriginalPrice(v) + return _u +} + +// SetNillableOriginalPrice sets the "original_price" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableOriginalPrice(v *float64) *SubscriptionPlanUpdate { + if v != nil { + _u.SetOriginalPrice(*v) + } + return _u +} + +// AddOriginalPrice adds value to the "original_price" field. +func (_u *SubscriptionPlanUpdate) AddOriginalPrice(v float64) *SubscriptionPlanUpdate { + _u.mutation.AddOriginalPrice(v) + return _u +} + +// ClearOriginalPrice clears the value of the "original_price" field. +func (_u *SubscriptionPlanUpdate) ClearOriginalPrice() *SubscriptionPlanUpdate { + _u.mutation.ClearOriginalPrice() + return _u +} + +// SetValidityDays sets the "validity_days" field. +func (_u *SubscriptionPlanUpdate) SetValidityDays(v int) *SubscriptionPlanUpdate { + _u.mutation.ResetValidityDays() + _u.mutation.SetValidityDays(v) + return _u +} + +// SetNillableValidityDays sets the "validity_days" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableValidityDays(v *int) *SubscriptionPlanUpdate { + if v != nil { + _u.SetValidityDays(*v) + } + return _u +} + +// AddValidityDays adds value to the "validity_days" field. +func (_u *SubscriptionPlanUpdate) AddValidityDays(v int) *SubscriptionPlanUpdate { + _u.mutation.AddValidityDays(v) + return _u +} + +// SetValidityUnit sets the "validity_unit" field. +func (_u *SubscriptionPlanUpdate) SetValidityUnit(v string) *SubscriptionPlanUpdate { + _u.mutation.SetValidityUnit(v) + return _u +} + +// SetNillableValidityUnit sets the "validity_unit" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableValidityUnit(v *string) *SubscriptionPlanUpdate { + if v != nil { + _u.SetValidityUnit(*v) + } + return _u +} + +// SetFeatures sets the "features" field. +func (_u *SubscriptionPlanUpdate) SetFeatures(v string) *SubscriptionPlanUpdate { + _u.mutation.SetFeatures(v) + return _u +} + +// SetNillableFeatures sets the "features" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableFeatures(v *string) *SubscriptionPlanUpdate { + if v != nil { + _u.SetFeatures(*v) + } + return _u +} + +// SetProductName sets the "product_name" field. +func (_u *SubscriptionPlanUpdate) SetProductName(v string) *SubscriptionPlanUpdate { + _u.mutation.SetProductName(v) + return _u +} + +// SetNillableProductName sets the "product_name" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableProductName(v *string) *SubscriptionPlanUpdate { + if v != nil { + _u.SetProductName(*v) + } + return _u +} + +// SetForSale sets the "for_sale" field. +func (_u *SubscriptionPlanUpdate) SetForSale(v bool) *SubscriptionPlanUpdate { + _u.mutation.SetForSale(v) + return _u +} + +// SetNillableForSale sets the "for_sale" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableForSale(v *bool) *SubscriptionPlanUpdate { + if v != nil { + _u.SetForSale(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *SubscriptionPlanUpdate) SetSortOrder(v int) *SubscriptionPlanUpdate { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *SubscriptionPlanUpdate) SetNillableSortOrder(v *int) *SubscriptionPlanUpdate { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *SubscriptionPlanUpdate) AddSortOrder(v int) *SubscriptionPlanUpdate { + _u.mutation.AddSortOrder(v) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *SubscriptionPlanUpdate) SetUpdatedAt(v time.Time) *SubscriptionPlanUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the SubscriptionPlanMutation object of the builder. +func (_u *SubscriptionPlanUpdate) Mutation() *SubscriptionPlanMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *SubscriptionPlanUpdate) 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 *SubscriptionPlanUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *SubscriptionPlanUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SubscriptionPlanUpdate) 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 *SubscriptionPlanUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := subscriptionplan.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SubscriptionPlanUpdate) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := subscriptionplan.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)} + } + } + if v, ok := _u.mutation.ValidityUnit(); ok { + if err := subscriptionplan.ValidityUnitValidator(v); err != nil { + return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)} + } + } + if v, ok := _u.mutation.ProductName(); ok { + if err := subscriptionplan.ProductNameValidator(v); err != nil { + return &ValidationError{Name: "product_name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.product_name": %w`, err)} + } + } + return nil +} + +func (_u *SubscriptionPlanUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(subscriptionplan.Table, subscriptionplan.Columns, sqlgraph.NewFieldSpec(subscriptionplan.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.GroupID(); ok { + _spec.SetField(subscriptionplan.FieldGroupID, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedGroupID(); ok { + _spec.AddField(subscriptionplan.FieldGroupID, field.TypeInt64, value) + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(subscriptionplan.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Description(); ok { + _spec.SetField(subscriptionplan.FieldDescription, field.TypeString, value) + } + if value, ok := _u.mutation.Price(); ok { + _spec.SetField(subscriptionplan.FieldPrice, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedPrice(); ok { + _spec.AddField(subscriptionplan.FieldPrice, field.TypeFloat64, value) + } + if value, ok := _u.mutation.OriginalPrice(); ok { + _spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedOriginalPrice(); ok { + _spec.AddField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value) + } + if _u.mutation.OriginalPriceCleared() { + _spec.ClearField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64) + } + if value, ok := _u.mutation.ValidityDays(); ok { + _spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedValidityDays(); ok { + _spec.AddField(subscriptionplan.FieldValidityDays, field.TypeInt, value) + } + if value, ok := _u.mutation.ValidityUnit(); ok { + _spec.SetField(subscriptionplan.FieldValidityUnit, field.TypeString, value) + } + if value, ok := _u.mutation.Features(); ok { + _spec.SetField(subscriptionplan.FieldFeatures, field.TypeString, value) + } + if value, ok := _u.mutation.ProductName(); ok { + _spec.SetField(subscriptionplan.FieldProductName, field.TypeString, value) + } + if value, ok := _u.mutation.ForSale(); ok { + _spec.SetField(subscriptionplan.FieldForSale, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(subscriptionplan.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(subscriptionplan.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(subscriptionplan.FieldUpdatedAt, field.TypeTime, value) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{subscriptionplan.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// SubscriptionPlanUpdateOne is the builder for updating a single SubscriptionPlan entity. +type SubscriptionPlanUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SubscriptionPlanMutation +} + +// SetGroupID sets the "group_id" field. +func (_u *SubscriptionPlanUpdateOne) SetGroupID(v int64) *SubscriptionPlanUpdateOne { + _u.mutation.ResetGroupID() + _u.mutation.SetGroupID(v) + return _u +} + +// SetNillableGroupID sets the "group_id" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableGroupID(v *int64) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetGroupID(*v) + } + return _u +} + +// AddGroupID adds value to the "group_id" field. +func (_u *SubscriptionPlanUpdateOne) AddGroupID(v int64) *SubscriptionPlanUpdateOne { + _u.mutation.AddGroupID(v) + return _u +} + +// SetName sets the "name" field. +func (_u *SubscriptionPlanUpdateOne) SetName(v string) *SubscriptionPlanUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableName(v *string) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetDescription sets the "description" field. +func (_u *SubscriptionPlanUpdateOne) SetDescription(v string) *SubscriptionPlanUpdateOne { + _u.mutation.SetDescription(v) + return _u +} + +// SetNillableDescription sets the "description" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableDescription(v *string) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetDescription(*v) + } + return _u +} + +// SetPrice sets the "price" field. +func (_u *SubscriptionPlanUpdateOne) SetPrice(v float64) *SubscriptionPlanUpdateOne { + _u.mutation.ResetPrice() + _u.mutation.SetPrice(v) + return _u +} + +// SetNillablePrice sets the "price" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillablePrice(v *float64) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetPrice(*v) + } + return _u +} + +// AddPrice adds value to the "price" field. +func (_u *SubscriptionPlanUpdateOne) AddPrice(v float64) *SubscriptionPlanUpdateOne { + _u.mutation.AddPrice(v) + return _u +} + +// SetOriginalPrice sets the "original_price" field. +func (_u *SubscriptionPlanUpdateOne) SetOriginalPrice(v float64) *SubscriptionPlanUpdateOne { + _u.mutation.ResetOriginalPrice() + _u.mutation.SetOriginalPrice(v) + return _u +} + +// SetNillableOriginalPrice sets the "original_price" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableOriginalPrice(v *float64) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetOriginalPrice(*v) + } + return _u +} + +// AddOriginalPrice adds value to the "original_price" field. +func (_u *SubscriptionPlanUpdateOne) AddOriginalPrice(v float64) *SubscriptionPlanUpdateOne { + _u.mutation.AddOriginalPrice(v) + return _u +} + +// ClearOriginalPrice clears the value of the "original_price" field. +func (_u *SubscriptionPlanUpdateOne) ClearOriginalPrice() *SubscriptionPlanUpdateOne { + _u.mutation.ClearOriginalPrice() + return _u +} + +// SetValidityDays sets the "validity_days" field. +func (_u *SubscriptionPlanUpdateOne) SetValidityDays(v int) *SubscriptionPlanUpdateOne { + _u.mutation.ResetValidityDays() + _u.mutation.SetValidityDays(v) + return _u +} + +// SetNillableValidityDays sets the "validity_days" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableValidityDays(v *int) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetValidityDays(*v) + } + return _u +} + +// AddValidityDays adds value to the "validity_days" field. +func (_u *SubscriptionPlanUpdateOne) AddValidityDays(v int) *SubscriptionPlanUpdateOne { + _u.mutation.AddValidityDays(v) + return _u +} + +// SetValidityUnit sets the "validity_unit" field. +func (_u *SubscriptionPlanUpdateOne) SetValidityUnit(v string) *SubscriptionPlanUpdateOne { + _u.mutation.SetValidityUnit(v) + return _u +} + +// SetNillableValidityUnit sets the "validity_unit" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableValidityUnit(v *string) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetValidityUnit(*v) + } + return _u +} + +// SetFeatures sets the "features" field. +func (_u *SubscriptionPlanUpdateOne) SetFeatures(v string) *SubscriptionPlanUpdateOne { + _u.mutation.SetFeatures(v) + return _u +} + +// SetNillableFeatures sets the "features" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableFeatures(v *string) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetFeatures(*v) + } + return _u +} + +// SetProductName sets the "product_name" field. +func (_u *SubscriptionPlanUpdateOne) SetProductName(v string) *SubscriptionPlanUpdateOne { + _u.mutation.SetProductName(v) + return _u +} + +// SetNillableProductName sets the "product_name" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableProductName(v *string) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetProductName(*v) + } + return _u +} + +// SetForSale sets the "for_sale" field. +func (_u *SubscriptionPlanUpdateOne) SetForSale(v bool) *SubscriptionPlanUpdateOne { + _u.mutation.SetForSale(v) + return _u +} + +// SetNillableForSale sets the "for_sale" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableForSale(v *bool) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetForSale(*v) + } + return _u +} + +// SetSortOrder sets the "sort_order" field. +func (_u *SubscriptionPlanUpdateOne) SetSortOrder(v int) *SubscriptionPlanUpdateOne { + _u.mutation.ResetSortOrder() + _u.mutation.SetSortOrder(v) + return _u +} + +// SetNillableSortOrder sets the "sort_order" field if the given value is not nil. +func (_u *SubscriptionPlanUpdateOne) SetNillableSortOrder(v *int) *SubscriptionPlanUpdateOne { + if v != nil { + _u.SetSortOrder(*v) + } + return _u +} + +// AddSortOrder adds value to the "sort_order" field. +func (_u *SubscriptionPlanUpdateOne) AddSortOrder(v int) *SubscriptionPlanUpdateOne { + _u.mutation.AddSortOrder(v) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *SubscriptionPlanUpdateOne) SetUpdatedAt(v time.Time) *SubscriptionPlanUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// Mutation returns the SubscriptionPlanMutation object of the builder. +func (_u *SubscriptionPlanUpdateOne) Mutation() *SubscriptionPlanMutation { + return _u.mutation +} + +// Where appends a list predicates to the SubscriptionPlanUpdate builder. +func (_u *SubscriptionPlanUpdateOne) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanUpdateOne { + _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 *SubscriptionPlanUpdateOne) Select(field string, fields ...string) *SubscriptionPlanUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated SubscriptionPlan entity. +func (_u *SubscriptionPlanUpdateOne) Save(ctx context.Context) (*SubscriptionPlan, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SubscriptionPlanUpdateOne) SaveX(ctx context.Context) *SubscriptionPlan { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *SubscriptionPlanUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SubscriptionPlanUpdateOne) 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 *SubscriptionPlanUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := subscriptionplan.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SubscriptionPlanUpdateOne) check() error { + if v, ok := _u.mutation.Name(); ok { + if err := subscriptionplan.NameValidator(v); err != nil { + return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)} + } + } + if v, ok := _u.mutation.ValidityUnit(); ok { + if err := subscriptionplan.ValidityUnitValidator(v); err != nil { + return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)} + } + } + if v, ok := _u.mutation.ProductName(); ok { + if err := subscriptionplan.ProductNameValidator(v); err != nil { + return &ValidationError{Name: "product_name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.product_name": %w`, err)} + } + } + return nil +} + +func (_u *SubscriptionPlanUpdateOne) sqlSave(ctx context.Context) (_node *SubscriptionPlan, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(subscriptionplan.Table, subscriptionplan.Columns, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SubscriptionPlan.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, subscriptionplan.FieldID) + for _, f := range fields { + if !subscriptionplan.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != subscriptionplan.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.GroupID(); ok { + _spec.SetField(subscriptionplan.FieldGroupID, field.TypeInt64, value) + } + if value, ok := _u.mutation.AddedGroupID(); ok { + _spec.AddField(subscriptionplan.FieldGroupID, field.TypeInt64, value) + } + if value, ok := _u.mutation.Name(); ok { + _spec.SetField(subscriptionplan.FieldName, field.TypeString, value) + } + if value, ok := _u.mutation.Description(); ok { + _spec.SetField(subscriptionplan.FieldDescription, field.TypeString, value) + } + if value, ok := _u.mutation.Price(); ok { + _spec.SetField(subscriptionplan.FieldPrice, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedPrice(); ok { + _spec.AddField(subscriptionplan.FieldPrice, field.TypeFloat64, value) + } + if value, ok := _u.mutation.OriginalPrice(); ok { + _spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedOriginalPrice(); ok { + _spec.AddField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value) + } + if _u.mutation.OriginalPriceCleared() { + _spec.ClearField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64) + } + if value, ok := _u.mutation.ValidityDays(); ok { + _spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedValidityDays(); ok { + _spec.AddField(subscriptionplan.FieldValidityDays, field.TypeInt, value) + } + if value, ok := _u.mutation.ValidityUnit(); ok { + _spec.SetField(subscriptionplan.FieldValidityUnit, field.TypeString, value) + } + if value, ok := _u.mutation.Features(); ok { + _spec.SetField(subscriptionplan.FieldFeatures, field.TypeString, value) + } + if value, ok := _u.mutation.ProductName(); ok { + _spec.SetField(subscriptionplan.FieldProductName, field.TypeString, value) + } + if value, ok := _u.mutation.ForSale(); ok { + _spec.SetField(subscriptionplan.FieldForSale, field.TypeBool, value) + } + if value, ok := _u.mutation.SortOrder(); ok { + _spec.SetField(subscriptionplan.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.AddedSortOrder(); ok { + _spec.AddField(subscriptionplan.FieldSortOrder, field.TypeInt, value) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(subscriptionplan.FieldUpdatedAt, field.TypeTime, value) + } + _node = &SubscriptionPlan{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{subscriptionplan.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/tx.go b/backend/ent/tx.go index b5aea447319be597090ad29c4cf1714f7a0a1736..bb3139d5c84119195c82c071194ca4b8663fbab0 100644 --- a/backend/ent/tx.go +++ b/backend/ent/tx.go @@ -30,6 +30,12 @@ type Tx struct { Group *GroupClient // IdempotencyRecord is the client for interacting with the IdempotencyRecord builders. IdempotencyRecord *IdempotencyRecordClient + // PaymentAuditLog is the client for interacting with the PaymentAuditLog builders. + PaymentAuditLog *PaymentAuditLogClient + // PaymentOrder is the client for interacting with the PaymentOrder builders. + PaymentOrder *PaymentOrderClient + // PaymentProviderInstance is the client for interacting with the PaymentProviderInstance builders. + PaymentProviderInstance *PaymentProviderInstanceClient // PromoCode is the client for interacting with the PromoCode builders. PromoCode *PromoCodeClient // PromoCodeUsage is the client for interacting with the PromoCodeUsage builders. @@ -42,6 +48,8 @@ type Tx struct { SecuritySecret *SecuritySecretClient // Setting is the client for interacting with the Setting builders. Setting *SettingClient + // SubscriptionPlan is the client for interacting with the SubscriptionPlan builders. + SubscriptionPlan *SubscriptionPlanClient // TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders. TLSFingerprintProfile *TLSFingerprintProfileClient // UsageCleanupTask is the client for interacting with the UsageCleanupTask builders. @@ -197,12 +205,16 @@ func (tx *Tx) init() { tx.ErrorPassthroughRule = NewErrorPassthroughRuleClient(tx.config) tx.Group = NewGroupClient(tx.config) tx.IdempotencyRecord = NewIdempotencyRecordClient(tx.config) + tx.PaymentAuditLog = NewPaymentAuditLogClient(tx.config) + tx.PaymentOrder = NewPaymentOrderClient(tx.config) + tx.PaymentProviderInstance = NewPaymentProviderInstanceClient(tx.config) tx.PromoCode = NewPromoCodeClient(tx.config) tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config) tx.Proxy = NewProxyClient(tx.config) tx.RedeemCode = NewRedeemCodeClient(tx.config) tx.SecuritySecret = NewSecuritySecretClient(tx.config) tx.Setting = NewSettingClient(tx.config) + tx.SubscriptionPlan = NewSubscriptionPlanClient(tx.config) tx.TLSFingerprintProfile = NewTLSFingerprintProfileClient(tx.config) tx.UsageCleanupTask = NewUsageCleanupTaskClient(tx.config) tx.UsageLog = NewUsageLogClient(tx.config) diff --git a/backend/ent/user.go b/backend/ent/user.go index 2435aa1b99874ff2fa33898eb111585223958ec3..a0eef2ba051f6ea9bb5e2e589d2cea2c5c7dfcb4 100644 --- a/backend/ent/user.go +++ b/backend/ent/user.go @@ -71,11 +71,13 @@ type UserEdges struct { AttributeValues []*UserAttributeValue `json:"attribute_values,omitempty"` // PromoCodeUsages holds the value of the promo_code_usages edge. PromoCodeUsages []*PromoCodeUsage `json:"promo_code_usages,omitempty"` + // PaymentOrders holds the value of the payment_orders edge. + PaymentOrders []*PaymentOrder `json:"payment_orders,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 [10]bool + loadedTypes [11]bool } // APIKeysOrErr returns the APIKeys value or an error if the edge @@ -159,10 +161,19 @@ func (e UserEdges) PromoCodeUsagesOrErr() ([]*PromoCodeUsage, error) { return nil, &NotLoadedError{edge: "promo_code_usages"} } +// PaymentOrdersOrErr returns the PaymentOrders value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) PaymentOrdersOrErr() ([]*PaymentOrder, error) { + if e.loadedTypes[9] { + return e.PaymentOrders, nil + } + return nil, &NotLoadedError{edge: "payment_orders"} +} + // 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[9] { + if e.loadedTypes[10] { return e.UserAllowedGroups, nil } return nil, &NotLoadedError{edge: "user_allowed_groups"} @@ -349,6 +360,11 @@ func (_m *User) QueryPromoCodeUsages() *PromoCodeUsageQuery { return NewUserClient(_m.config).QueryPromoCodeUsages(_m) } +// QueryPaymentOrders queries the "payment_orders" edge of the User entity. +func (_m *User) QueryPaymentOrders() *PaymentOrderQuery { + return NewUserClient(_m.config).QueryPaymentOrders(_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 ae9418ff0739e4261d28217b92d0bf010f58bdef..338518a841c6837dc756b57bbeb8d539529a44ef 100644 --- a/backend/ent/user/user.go +++ b/backend/ent/user/user.go @@ -61,6 +61,8 @@ const ( EdgeAttributeValues = "attribute_values" // EdgePromoCodeUsages holds the string denoting the promo_code_usages edge name in mutations. EdgePromoCodeUsages = "promo_code_usages" + // EdgePaymentOrders holds the string denoting the payment_orders edge name in mutations. + EdgePaymentOrders = "payment_orders" // 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. @@ -126,6 +128,13 @@ const ( PromoCodeUsagesInverseTable = "promo_code_usages" // PromoCodeUsagesColumn is the table column denoting the promo_code_usages relation/edge. PromoCodeUsagesColumn = "user_id" + // PaymentOrdersTable is the table that holds the payment_orders relation/edge. + PaymentOrdersTable = "payment_orders" + // PaymentOrdersInverseTable is the table name for the PaymentOrder entity. + // It exists in this package in order to avoid circular dependency with the "paymentorder" package. + PaymentOrdersInverseTable = "payment_orders" + // PaymentOrdersColumn is the table column denoting the payment_orders relation/edge. + PaymentOrdersColumn = "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. @@ -414,6 +423,20 @@ func ByPromoCodeUsages(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { } } +// ByPaymentOrdersCount orders the results by payment_orders count. +func ByPaymentOrdersCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newPaymentOrdersStep(), opts...) + } +} + +// ByPaymentOrders orders the results by payment_orders terms. +func ByPaymentOrders(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newPaymentOrdersStep(), 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) { @@ -490,6 +513,13 @@ func newPromoCodeUsagesStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, PromoCodeUsagesTable, PromoCodeUsagesColumn), ) } +func newPaymentOrdersStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(PaymentOrdersInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, PaymentOrdersTable, PaymentOrdersColumn), + ) +} 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 1de61037022a66b83d16753f95cac878dae8bcdc..b1d1000f14eafcd3fb94d0497b43320f7c395b9c 100644 --- a/backend/ent/user/where.go +++ b/backend/ent/user/where.go @@ -1067,6 +1067,29 @@ func HasPromoCodeUsagesWith(preds ...predicate.PromoCodeUsage) predicate.User { }) } +// HasPaymentOrders applies the HasEdge predicate on the "payment_orders" edge. +func HasPaymentOrders() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, PaymentOrdersTable, PaymentOrdersColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasPaymentOrdersWith applies the HasEdge predicate on the "payment_orders" edge with a given conditions (other predicates). +func HasPaymentOrdersWith(preds ...predicate.PaymentOrder) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newPaymentOrdersStep() + 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 f862a580c524477a23c2e414976e531c47a5c298..7f1c5df141196492b9a0bb05d0fe53fd340c4755 100644 --- a/backend/ent/user_create.go +++ b/backend/ent/user_create.go @@ -14,6 +14,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/announcementread" "github.com/Wei-Shaw/sub2api/ent/apikey" "github.com/Wei-Shaw/sub2api/ent/group" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" "github.com/Wei-Shaw/sub2api/ent/promocodeusage" "github.com/Wei-Shaw/sub2api/ent/redeemcode" "github.com/Wei-Shaw/sub2api/ent/usagelog" @@ -345,6 +346,21 @@ func (_c *UserCreate) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserCreate { return _c.AddPromoCodeUsageIDs(ids...) } +// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by IDs. +func (_c *UserCreate) AddPaymentOrderIDs(ids ...int64) *UserCreate { + _c.mutation.AddPaymentOrderIDs(ids...) + return _c +} + +// AddPaymentOrders adds the "payment_orders" edges to the PaymentOrder entity. +func (_c *UserCreate) AddPaymentOrders(v ...*PaymentOrder) *UserCreate { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddPaymentOrderIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (_c *UserCreate) Mutation() *UserMutation { return _c.mutation @@ -718,6 +734,22 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := _c.mutation.PaymentOrdersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.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 4b56e16f43545c9bf852a012e410675215860114..113d87aca24a273a137326557955a6b4eb60b751 100644 --- a/backend/ent/user_query.go +++ b/backend/ent/user_query.go @@ -16,6 +16,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/announcementread" "github.com/Wei-Shaw/sub2api/ent/apikey" "github.com/Wei-Shaw/sub2api/ent/group" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" "github.com/Wei-Shaw/sub2api/ent/predicate" "github.com/Wei-Shaw/sub2api/ent/promocodeusage" "github.com/Wei-Shaw/sub2api/ent/redeemcode" @@ -42,6 +43,7 @@ type UserQuery struct { withUsageLogs *UsageLogQuery withAttributeValues *UserAttributeValueQuery withPromoCodeUsages *PromoCodeUsageQuery + withPaymentOrders *PaymentOrderQuery withUserAllowedGroups *UserAllowedGroupQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). @@ -278,6 +280,28 @@ func (_q *UserQuery) QueryPromoCodeUsages() *PromoCodeUsageQuery { return query } +// QueryPaymentOrders chains the current query on the "payment_orders" edge. +func (_q *UserQuery) QueryPaymentOrders() *PaymentOrderQuery { + query := (&PaymentOrderClient{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(paymentorder.Table, paymentorder.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.PaymentOrdersTable, user.PaymentOrdersColumn), + ) + 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() @@ -501,6 +525,7 @@ func (_q *UserQuery) Clone() *UserQuery { withUsageLogs: _q.withUsageLogs.Clone(), withAttributeValues: _q.withAttributeValues.Clone(), withPromoCodeUsages: _q.withPromoCodeUsages.Clone(), + withPaymentOrders: _q.withPaymentOrders.Clone(), withUserAllowedGroups: _q.withUserAllowedGroups.Clone(), // clone intermediate query. sql: _q.sql.Clone(), @@ -607,6 +632,17 @@ func (_q *UserQuery) WithPromoCodeUsages(opts ...func(*PromoCodeUsageQuery)) *Us return _q } +// WithPaymentOrders tells the query-builder to eager-load the nodes that are connected to +// the "payment_orders" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *UserQuery) WithPaymentOrders(opts ...func(*PaymentOrderQuery)) *UserQuery { + query := (&PaymentOrderClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withPaymentOrders = 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 { @@ -696,7 +732,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e var ( nodes = []*User{} _spec = _q.querySpec() - loadedTypes = [10]bool{ + loadedTypes = [11]bool{ _q.withAPIKeys != nil, _q.withRedeemCodes != nil, _q.withSubscriptions != nil, @@ -706,6 +742,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e _q.withUsageLogs != nil, _q.withAttributeValues != nil, _q.withPromoCodeUsages != nil, + _q.withPaymentOrders != nil, _q.withUserAllowedGroups != nil, } ) @@ -795,6 +832,13 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e return nil, err } } + if query := _q.withPaymentOrders; query != nil { + if err := _q.loadPaymentOrders(ctx, query, nodes, + func(n *User) { n.Edges.PaymentOrders = []*PaymentOrder{} }, + func(n *User, e *PaymentOrder) { n.Edges.PaymentOrders = append(n.Edges.PaymentOrders, 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{} }, @@ -1112,6 +1156,36 @@ func (_q *UserQuery) loadPromoCodeUsages(ctx context.Context, query *PromoCodeUs } return nil } +func (_q *UserQuery) loadPaymentOrders(ctx context.Context, query *PaymentOrderQuery, nodes []*User, init func(*User), assign func(*User, *PaymentOrder)) 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(paymentorder.FieldUserID) + } + query.Where(predicate.PaymentOrder(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.PaymentOrdersColumn), 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) diff --git a/backend/ent/user_update.go b/backend/ent/user_update.go index 80222c92dc30d4ce8c1ee448cd221653cfa94872..8107c980a8a574c8fcd3ab204260fd3ec9b71c49 100644 --- a/backend/ent/user_update.go +++ b/backend/ent/user_update.go @@ -14,6 +14,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/announcementread" "github.com/Wei-Shaw/sub2api/ent/apikey" "github.com/Wei-Shaw/sub2api/ent/group" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" "github.com/Wei-Shaw/sub2api/ent/predicate" "github.com/Wei-Shaw/sub2api/ent/promocodeusage" "github.com/Wei-Shaw/sub2api/ent/redeemcode" @@ -377,6 +378,21 @@ func (_u *UserUpdate) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate { return _u.AddPromoCodeUsageIDs(ids...) } +// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by IDs. +func (_u *UserUpdate) AddPaymentOrderIDs(ids ...int64) *UserUpdate { + _u.mutation.AddPaymentOrderIDs(ids...) + return _u +} + +// AddPaymentOrders adds the "payment_orders" edges to the PaymentOrder entity. +func (_u *UserUpdate) AddPaymentOrders(v ...*PaymentOrder) *UserUpdate { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddPaymentOrderIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (_u *UserUpdate) Mutation() *UserMutation { return _u.mutation @@ -571,6 +587,27 @@ func (_u *UserUpdate) RemovePromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate { return _u.RemovePromoCodeUsageIDs(ids...) } +// ClearPaymentOrders clears all "payment_orders" edges to the PaymentOrder entity. +func (_u *UserUpdate) ClearPaymentOrders() *UserUpdate { + _u.mutation.ClearPaymentOrders() + return _u +} + +// RemovePaymentOrderIDs removes the "payment_orders" edge to PaymentOrder entities by IDs. +func (_u *UserUpdate) RemovePaymentOrderIDs(ids ...int64) *UserUpdate { + _u.mutation.RemovePaymentOrderIDs(ids...) + return _u +} + +// RemovePaymentOrders removes "payment_orders" edges to PaymentOrder entities. +func (_u *UserUpdate) RemovePaymentOrders(v ...*PaymentOrder) *UserUpdate { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemovePaymentOrderIDs(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 { @@ -1126,6 +1163,51 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.PaymentOrdersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedPaymentOrdersIDs(); len(nodes) > 0 && !_u.mutation.PaymentOrdersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.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.PaymentOrdersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.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} @@ -1487,6 +1569,21 @@ func (_u *UserUpdateOne) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserUpdateOne return _u.AddPromoCodeUsageIDs(ids...) } +// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by IDs. +func (_u *UserUpdateOne) AddPaymentOrderIDs(ids ...int64) *UserUpdateOne { + _u.mutation.AddPaymentOrderIDs(ids...) + return _u +} + +// AddPaymentOrders adds the "payment_orders" edges to the PaymentOrder entity. +func (_u *UserUpdateOne) AddPaymentOrders(v ...*PaymentOrder) *UserUpdateOne { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddPaymentOrderIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (_u *UserUpdateOne) Mutation() *UserMutation { return _u.mutation @@ -1681,6 +1778,27 @@ func (_u *UserUpdateOne) RemovePromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate return _u.RemovePromoCodeUsageIDs(ids...) } +// ClearPaymentOrders clears all "payment_orders" edges to the PaymentOrder entity. +func (_u *UserUpdateOne) ClearPaymentOrders() *UserUpdateOne { + _u.mutation.ClearPaymentOrders() + return _u +} + +// RemovePaymentOrderIDs removes the "payment_orders" edge to PaymentOrder entities by IDs. +func (_u *UserUpdateOne) RemovePaymentOrderIDs(ids ...int64) *UserUpdateOne { + _u.mutation.RemovePaymentOrderIDs(ids...) + return _u +} + +// RemovePaymentOrders removes "payment_orders" edges to PaymentOrder entities. +func (_u *UserUpdateOne) RemovePaymentOrders(v ...*PaymentOrder) *UserUpdateOne { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemovePaymentOrderIDs(ids...) +} + // Where appends a list predicates to the UserUpdate builder. func (_u *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { _u.mutation.Where(ps...) @@ -2266,6 +2384,51 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.PaymentOrdersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedPaymentOrdersIDs(); len(nodes) > 0 && !_u.mutation.PaymentOrdersCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.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.PaymentOrdersIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.PaymentOrdersTable, + Columns: []string{user.PaymentOrdersColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(paymentorder.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/go.mod b/backend/go.mod index c4fc52f130e0c6a0a5caee939f26c8bb9fe1319e..66b6cc25b598efeb750d0d5b36153f8c95bd6863 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -27,12 +27,16 @@ require ( github.com/refraction-networking/utls v1.8.2 github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v4 v4.25.6 + github.com/shopspring/decimal v1.4.0 + github.com/smartwalle/alipay/v3 v3.2.29 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.11.1 + github.com/stripe/stripe-go/v85 v85.0.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 github.com/testcontainers/testcontainers-go/modules/redis v0.40.0 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 + github.com/wechatpay-apiv3/wechatpay-go v0.2.21 github.com/zeromicro/go-zero v1.9.4 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.48.0 @@ -99,6 +103,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/subcommands v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl/v2 v2.18.1 // indirect @@ -137,6 +142,9 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smartwalle/ncrypto v1.0.4 // indirect + github.com/smartwalle/ngx v1.1.0 // indirect + github.com/smartwalle/nsign v1.0.9 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -167,6 +175,7 @@ require ( golang.org/x/mod v0.32.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.41.0 // indirect google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 996a4b6db7956b7a1237768cbcb83af1d873c614..e4496f2c0b590e2f17f30a5827fc3b1fc1beef4d 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -14,6 +14,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= +github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= github.com/alitto/pond/v2 v2.6.2 h1:Sphe40g0ILeM1pA2c2K+Th0DGU+pt0A/Kprr+WB24Pw= github.com/alitto/pond/v2 v2.6.2/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= @@ -160,6 +162,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= @@ -288,8 +292,18 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartwalle/alipay/v3 v3.2.29 h1:roGFqlml8hDa//0TpFmlyxZhndTYs7rbYLu/HlNFNJo= +github.com/smartwalle/alipay/v3 v3.2.29/go.mod h1:XarBLuAkwK3ah7mYjVtghRu+ysxzlex9sRkgqNMzMRU= +github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8= +github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk= +github.com/smartwalle/ngx v1.1.0 h1:q8nANgWSPRGeI/u+ixBoA4mf68DrUq6vZ+n9L5UKv9I= +github.com/smartwalle/ngx v1.1.0/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= +github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E= +github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= @@ -317,6 +331,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stripe/stripe-go/v85 v85.0.0 h1:HMlFJXW6I/9WvkeSAtj8V7dI5pzeDu4gS1TaqR1ccI4= +github.com/stripe/stripe-go/v85 v85.0.0/go.mod h1:5P+HGFenpWgak27T5Is6JMsmDfUC1yJnjhhmquz7kXw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= @@ -342,6 +358,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs= +github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 9b430377890eca44007b1f4ee4d05a9361c4e8c8..bc4e5e460c3903be71287aa0bd0797af955bbfe7 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -65,6 +65,7 @@ type Config struct { JWT JWTConfig `mapstructure:"jwt"` Totp TotpConfig `mapstructure:"totp"` LinuxDo LinuxDoConnectConfig `mapstructure:"linuxdo_connect"` + OIDC OIDCConnectConfig `mapstructure:"oidc_connect"` Default DefaultConfig `mapstructure:"default"` RateLimit RateLimitConfig `mapstructure:"rate_limit"` Pricing PricingConfig `mapstructure:"pricing"` @@ -184,6 +185,34 @@ type LinuxDoConnectConfig struct { UserInfoUsernamePath string `mapstructure:"userinfo_username_path"` } +type OIDCConnectConfig struct { + Enabled bool `mapstructure:"enabled"` + ProviderName string `mapstructure:"provider_name"` // 显示名: "Keycloak" 等 + ClientID string `mapstructure:"client_id"` + ClientSecret string `mapstructure:"client_secret"` + IssuerURL string `mapstructure:"issuer_url"` + DiscoveryURL string `mapstructure:"discovery_url"` + AuthorizeURL string `mapstructure:"authorize_url"` + TokenURL string `mapstructure:"token_url"` + UserInfoURL string `mapstructure:"userinfo_url"` + JWKSURL string `mapstructure:"jwks_url"` + Scopes string `mapstructure:"scopes"` // 默认 "openid email profile" + RedirectURL string `mapstructure:"redirect_url"` // 后端回调地址(需在提供方后台登记) + FrontendRedirectURL string `mapstructure:"frontend_redirect_url"` // 前端接收 token 的路由(默认:/auth/oidc/callback) + TokenAuthMethod string `mapstructure:"token_auth_method"` // client_secret_post / client_secret_basic / none + UsePKCE bool `mapstructure:"use_pkce"` + ValidateIDToken bool `mapstructure:"validate_id_token"` + AllowedSigningAlgs string `mapstructure:"allowed_signing_algs"` // 默认 "RS256,ES256,PS256" + ClockSkewSeconds int `mapstructure:"clock_skew_seconds"` // 默认 120 + RequireEmailVerified bool `mapstructure:"require_email_verified"` // 默认 false + + // 可选:用于从 userinfo JSON 中提取字段的 gjson 路径。 + // 为空时,服务端会尝试一组常见字段名。 + UserInfoEmailPath string `mapstructure:"userinfo_email_path"` + UserInfoIDPath string `mapstructure:"userinfo_id_path"` + UserInfoUsernamePath string `mapstructure:"userinfo_username_path"` +} + // TokenRefreshConfig OAuth token自动刷新配置 type TokenRefreshConfig struct { // 是否启用自动刷新 @@ -318,6 +347,12 @@ type GatewayConfig struct { // ForceCodexCLI: 强制将 OpenAI `/v1/responses` 请求按 Codex CLI 处理。 // 用于网关未透传/改写 User-Agent 时的兼容兜底(默认关闭,避免影响其他客户端)。 ForceCodexCLI bool `mapstructure:"force_codex_cli"` + // ForcedCodexInstructionsTemplateFile: 服务端强制附加到 Codex 顶层 instructions 的模板文件路径。 + // 模板渲染后会直接覆盖最终 instructions;若需要保留客户端 system 转换结果,请在模板中显式引用 {{ .ExistingInstructions }}。 + ForcedCodexInstructionsTemplateFile string `mapstructure:"forced_codex_instructions_template_file"` + // ForcedCodexInstructionsTemplate: 启动时从模板文件读取并缓存的模板内容。 + // 该字段不直接参与配置反序列化,仅用于请求热路径避免重复读盘。 + ForcedCodexInstructionsTemplate string `mapstructure:"-"` // OpenAIPassthroughAllowTimeoutHeaders: OpenAI 透传模式是否放行客户端超时头 // 关闭(默认)可避免 x-stainless-timeout 等头导致上游提前断流。 OpenAIPassthroughAllowTimeoutHeaders bool `mapstructure:"openai_passthrough_allow_timeout_headers"` @@ -620,6 +655,10 @@ type GatewaySchedulingConfig struct { // 负载计算 LoadBatchEnabled bool `mapstructure:"load_batch_enabled"` + // 快照桶读取时的 MGET 分块大小 + SnapshotMGetChunkSize int `mapstructure:"snapshot_mget_chunk_size"` + // 快照重建时的缓存写入分块大小 + SnapshotWriteChunkSize int `mapstructure:"snapshot_write_chunk_size"` // 过期槽位清理周期(0 表示禁用) SlotCleanupInterval time.Duration `mapstructure:"slot_cleanup_interval"` @@ -968,6 +1007,23 @@ func load(allowMissingJWTSecret bool) (*Config, error) { cfg.LinuxDo.UserInfoEmailPath = strings.TrimSpace(cfg.LinuxDo.UserInfoEmailPath) cfg.LinuxDo.UserInfoIDPath = strings.TrimSpace(cfg.LinuxDo.UserInfoIDPath) cfg.LinuxDo.UserInfoUsernamePath = strings.TrimSpace(cfg.LinuxDo.UserInfoUsernamePath) + cfg.OIDC.ProviderName = strings.TrimSpace(cfg.OIDC.ProviderName) + cfg.OIDC.ClientID = strings.TrimSpace(cfg.OIDC.ClientID) + cfg.OIDC.ClientSecret = strings.TrimSpace(cfg.OIDC.ClientSecret) + cfg.OIDC.IssuerURL = strings.TrimSpace(cfg.OIDC.IssuerURL) + cfg.OIDC.DiscoveryURL = strings.TrimSpace(cfg.OIDC.DiscoveryURL) + cfg.OIDC.AuthorizeURL = strings.TrimSpace(cfg.OIDC.AuthorizeURL) + cfg.OIDC.TokenURL = strings.TrimSpace(cfg.OIDC.TokenURL) + cfg.OIDC.UserInfoURL = strings.TrimSpace(cfg.OIDC.UserInfoURL) + cfg.OIDC.JWKSURL = strings.TrimSpace(cfg.OIDC.JWKSURL) + cfg.OIDC.Scopes = strings.TrimSpace(cfg.OIDC.Scopes) + cfg.OIDC.RedirectURL = strings.TrimSpace(cfg.OIDC.RedirectURL) + cfg.OIDC.FrontendRedirectURL = strings.TrimSpace(cfg.OIDC.FrontendRedirectURL) + cfg.OIDC.TokenAuthMethod = strings.ToLower(strings.TrimSpace(cfg.OIDC.TokenAuthMethod)) + cfg.OIDC.AllowedSigningAlgs = strings.TrimSpace(cfg.OIDC.AllowedSigningAlgs) + cfg.OIDC.UserInfoEmailPath = strings.TrimSpace(cfg.OIDC.UserInfoEmailPath) + cfg.OIDC.UserInfoIDPath = strings.TrimSpace(cfg.OIDC.UserInfoIDPath) + cfg.OIDC.UserInfoUsernamePath = strings.TrimSpace(cfg.OIDC.UserInfoUsernamePath) cfg.Dashboard.KeyPrefix = strings.TrimSpace(cfg.Dashboard.KeyPrefix) cfg.CORS.AllowedOrigins = normalizeStringSlice(cfg.CORS.AllowedOrigins) cfg.Security.ResponseHeaders.AdditionalAllowed = normalizeStringSlice(cfg.Security.ResponseHeaders.AdditionalAllowed) @@ -979,6 +1035,14 @@ func load(allowMissingJWTSecret bool) (*Config, error) { cfg.Log.Environment = strings.TrimSpace(cfg.Log.Environment) cfg.Log.StacktraceLevel = strings.ToLower(strings.TrimSpace(cfg.Log.StacktraceLevel)) cfg.Log.Output.FilePath = strings.TrimSpace(cfg.Log.Output.FilePath) + cfg.Gateway.ForcedCodexInstructionsTemplateFile = strings.TrimSpace(cfg.Gateway.ForcedCodexInstructionsTemplateFile) + if cfg.Gateway.ForcedCodexInstructionsTemplateFile != "" { + content, err := os.ReadFile(cfg.Gateway.ForcedCodexInstructionsTemplateFile) + if err != nil { + return nil, fmt.Errorf("read forced codex instructions template %q: %w", cfg.Gateway.ForcedCodexInstructionsTemplateFile, err) + } + cfg.Gateway.ForcedCodexInstructionsTemplate = string(content) + } // 兼容旧键 gateway.openai_ws.sticky_previous_response_ttl_seconds。 // 新键未配置(<=0)时回退旧键;新键优先。 @@ -1138,6 +1202,30 @@ func setDefaults() { viper.SetDefault("linuxdo_connect.userinfo_id_path", "") viper.SetDefault("linuxdo_connect.userinfo_username_path", "") + // Generic OIDC OAuth 登录 + viper.SetDefault("oidc_connect.enabled", false) + viper.SetDefault("oidc_connect.provider_name", "OIDC") + viper.SetDefault("oidc_connect.client_id", "") + viper.SetDefault("oidc_connect.client_secret", "") + viper.SetDefault("oidc_connect.issuer_url", "") + viper.SetDefault("oidc_connect.discovery_url", "") + viper.SetDefault("oidc_connect.authorize_url", "") + viper.SetDefault("oidc_connect.token_url", "") + viper.SetDefault("oidc_connect.userinfo_url", "") + viper.SetDefault("oidc_connect.jwks_url", "") + viper.SetDefault("oidc_connect.scopes", "openid email profile") + viper.SetDefault("oidc_connect.redirect_url", "") + viper.SetDefault("oidc_connect.frontend_redirect_url", "/auth/oidc/callback") + viper.SetDefault("oidc_connect.token_auth_method", "client_secret_post") + viper.SetDefault("oidc_connect.use_pkce", false) + viper.SetDefault("oidc_connect.validate_id_token", true) + viper.SetDefault("oidc_connect.allowed_signing_algs", "RS256,ES256,PS256") + viper.SetDefault("oidc_connect.clock_skew_seconds", 120) + viper.SetDefault("oidc_connect.require_email_verified", false) + viper.SetDefault("oidc_connect.userinfo_email_path", "") + viper.SetDefault("oidc_connect.userinfo_id_path", "") + viper.SetDefault("oidc_connect.userinfo_username_path", "") + // Database viper.SetDefault("database.host", "localhost") viper.SetDefault("database.port", 5432) @@ -1340,6 +1428,8 @@ func setDefaults() { viper.SetDefault("gateway.scheduling.fallback_max_waiting", 100) viper.SetDefault("gateway.scheduling.fallback_selection_mode", "last_used") viper.SetDefault("gateway.scheduling.load_batch_enabled", true) + viper.SetDefault("gateway.scheduling.snapshot_mget_chunk_size", 128) + viper.SetDefault("gateway.scheduling.snapshot_write_chunk_size", 256) viper.SetDefault("gateway.scheduling.slot_cleanup_interval", 30*time.Second) viper.SetDefault("gateway.scheduling.db_fallback_enabled", true) viper.SetDefault("gateway.scheduling.db_fallback_timeout_seconds", 0) @@ -1572,6 +1662,87 @@ func (c *Config) Validate() error { warnIfInsecureURL("linuxdo_connect.redirect_url", c.LinuxDo.RedirectURL) warnIfInsecureURL("linuxdo_connect.frontend_redirect_url", c.LinuxDo.FrontendRedirectURL) } + if c.OIDC.Enabled { + if strings.TrimSpace(c.OIDC.ClientID) == "" { + return fmt.Errorf("oidc_connect.client_id is required when oidc_connect.enabled=true") + } + if strings.TrimSpace(c.OIDC.IssuerURL) == "" { + return fmt.Errorf("oidc_connect.issuer_url is required when oidc_connect.enabled=true") + } + if strings.TrimSpace(c.OIDC.RedirectURL) == "" { + return fmt.Errorf("oidc_connect.redirect_url is required when oidc_connect.enabled=true") + } + if strings.TrimSpace(c.OIDC.FrontendRedirectURL) == "" { + return fmt.Errorf("oidc_connect.frontend_redirect_url is required when oidc_connect.enabled=true") + } + if !scopeContainsOpenID(c.OIDC.Scopes) { + return fmt.Errorf("oidc_connect.scopes must contain openid") + } + + method := strings.ToLower(strings.TrimSpace(c.OIDC.TokenAuthMethod)) + switch method { + case "", "client_secret_post", "client_secret_basic", "none": + default: + return fmt.Errorf("oidc_connect.token_auth_method must be one of: client_secret_post/client_secret_basic/none") + } + if method == "none" && !c.OIDC.UsePKCE { + return fmt.Errorf("oidc_connect.use_pkce must be true when oidc_connect.token_auth_method=none") + } + if (method == "" || method == "client_secret_post" || method == "client_secret_basic") && + strings.TrimSpace(c.OIDC.ClientSecret) == "" { + return fmt.Errorf("oidc_connect.client_secret is required when oidc_connect.enabled=true and token_auth_method is client_secret_post/client_secret_basic") + } + if c.OIDC.ClockSkewSeconds < 0 || c.OIDC.ClockSkewSeconds > 600 { + return fmt.Errorf("oidc_connect.clock_skew_seconds must be between 0 and 600") + } + if c.OIDC.ValidateIDToken && strings.TrimSpace(c.OIDC.AllowedSigningAlgs) == "" { + return fmt.Errorf("oidc_connect.allowed_signing_algs is required when oidc_connect.validate_id_token=true") + } + + if err := ValidateAbsoluteHTTPURL(c.OIDC.IssuerURL); err != nil { + return fmt.Errorf("oidc_connect.issuer_url invalid: %w", err) + } + if v := strings.TrimSpace(c.OIDC.DiscoveryURL); v != "" { + if err := ValidateAbsoluteHTTPURL(v); err != nil { + return fmt.Errorf("oidc_connect.discovery_url invalid: %w", err) + } + } + if v := strings.TrimSpace(c.OIDC.AuthorizeURL); v != "" { + if err := ValidateAbsoluteHTTPURL(v); err != nil { + return fmt.Errorf("oidc_connect.authorize_url invalid: %w", err) + } + } + if v := strings.TrimSpace(c.OIDC.TokenURL); v != "" { + if err := ValidateAbsoluteHTTPURL(v); err != nil { + return fmt.Errorf("oidc_connect.token_url invalid: %w", err) + } + } + if v := strings.TrimSpace(c.OIDC.UserInfoURL); v != "" { + if err := ValidateAbsoluteHTTPURL(v); err != nil { + return fmt.Errorf("oidc_connect.userinfo_url invalid: %w", err) + } + } + if v := strings.TrimSpace(c.OIDC.JWKSURL); v != "" { + if err := ValidateAbsoluteHTTPURL(v); err != nil { + return fmt.Errorf("oidc_connect.jwks_url invalid: %w", err) + } + } + if err := ValidateAbsoluteHTTPURL(c.OIDC.RedirectURL); err != nil { + return fmt.Errorf("oidc_connect.redirect_url invalid: %w", err) + } + if err := ValidateFrontendRedirectURL(c.OIDC.FrontendRedirectURL); err != nil { + return fmt.Errorf("oidc_connect.frontend_redirect_url invalid: %w", err) + } + + warnIfInsecureURL("oidc_connect.issuer_url", c.OIDC.IssuerURL) + warnIfInsecureURL("oidc_connect.discovery_url", c.OIDC.DiscoveryURL) + warnIfInsecureURL("oidc_connect.authorize_url", c.OIDC.AuthorizeURL) + warnIfInsecureURL("oidc_connect.token_url", c.OIDC.TokenURL) + warnIfInsecureURL("oidc_connect.userinfo_url", c.OIDC.UserInfoURL) + warnIfInsecureURL("oidc_connect.jwks_url", c.OIDC.JWKSURL) + warnIfInsecureURL("oidc_connect.redirect_url", c.OIDC.RedirectURL) + warnIfInsecureURL("oidc_connect.frontend_redirect_url", c.OIDC.FrontendRedirectURL) + } if c.Billing.CircuitBreaker.Enabled { if c.Billing.CircuitBreaker.FailureThreshold <= 0 { return fmt.Errorf("billing.circuit_breaker.failure_threshold must be positive") @@ -2001,6 +2172,12 @@ func (c *Config) Validate() error { if c.Gateway.Scheduling.FallbackMaxWaiting <= 0 { return fmt.Errorf("gateway.scheduling.fallback_max_waiting must be positive") } + if c.Gateway.Scheduling.SnapshotMGetChunkSize <= 0 { + return fmt.Errorf("gateway.scheduling.snapshot_mget_chunk_size must be positive") + } + if c.Gateway.Scheduling.SnapshotWriteChunkSize <= 0 { + return fmt.Errorf("gateway.scheduling.snapshot_write_chunk_size must be positive") + } if c.Gateway.Scheduling.SlotCleanupInterval < 0 { return fmt.Errorf("gateway.scheduling.slot_cleanup_interval must be non-negative") } @@ -2184,6 +2361,15 @@ func ValidateFrontendRedirectURL(raw string) error { return nil } +func scopeContainsOpenID(scopes string) bool { + for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) { + if scope == "openid" { + return true + } + } + return false +} + // isHTTPScheme 检查是否为 HTTP 或 HTTPS 协议 func isHTTPScheme(scheme string) bool { return strings.EqualFold(scheme, "http") || strings.EqualFold(scheme, "https") diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index 2de5451ee031f166581e4acc33d9a5fbbef30ed8..fe181a2f30198c82cefa60e8910ace563ba6d009 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -1,6 +1,8 @@ package config import ( + "os" + "path/filepath" "strings" "testing" "time" @@ -223,6 +225,23 @@ func TestLoadSchedulingConfigFromEnv(t *testing.T) { } } +func TestLoadForcedCodexInstructionsTemplate(t *testing.T) { + resetViperWithJWTSecret(t) + + tempDir := t.TempDir() + templatePath := filepath.Join(tempDir, "codex-instructions.md.tmpl") + configPath := filepath.Join(tempDir, "config.yaml") + + require.NoError(t, os.WriteFile(templatePath, []byte("server-prefix\n\n{{ .ExistingInstructions }}"), 0o644)) + require.NoError(t, os.WriteFile(configPath, []byte("gateway:\n forced_codex_instructions_template_file: \""+templatePath+"\"\n"), 0o644)) + t.Setenv("DATA_DIR", tempDir) + + cfg, err := Load() + require.NoError(t, err) + require.Equal(t, templatePath, cfg.Gateway.ForcedCodexInstructionsTemplateFile) + require.Equal(t, "server-prefix\n\n{{ .ExistingInstructions }}", cfg.Gateway.ForcedCodexInstructionsTemplate) +} + func TestLoadDefaultSecurityToggles(t *testing.T) { resetViperWithJWTSecret(t) @@ -351,6 +370,60 @@ func TestValidateLinuxDoPKCERequiredForPublicClient(t *testing.T) { } } +func TestValidateOIDCScopesMustContainOpenID(t *testing.T) { + resetViperWithJWTSecret(t) + + cfg, err := Load() + if err != nil { + t.Fatalf("Load() error: %v", err) + } + + cfg.OIDC.Enabled = true + cfg.OIDC.ClientID = "oidc-client" + cfg.OIDC.ClientSecret = "oidc-secret" + cfg.OIDC.IssuerURL = "https://issuer.example.com" + cfg.OIDC.AuthorizeURL = "https://issuer.example.com/auth" + cfg.OIDC.TokenURL = "https://issuer.example.com/token" + cfg.OIDC.JWKSURL = "https://issuer.example.com/jwks" + cfg.OIDC.RedirectURL = "https://example.com/api/v1/auth/oauth/oidc/callback" + cfg.OIDC.FrontendRedirectURL = "/auth/oidc/callback" + cfg.OIDC.Scopes = "profile email" + + err = cfg.Validate() + if err == nil { + t.Fatalf("Validate() expected error when scopes do not include openid, got nil") + } + if !strings.Contains(err.Error(), "oidc_connect.scopes") { + t.Fatalf("Validate() expected oidc_connect.scopes error, got: %v", err) + } +} + +func TestValidateOIDCAllowsIssuerOnlyEndpointsWithDiscoveryFallback(t *testing.T) { + resetViperWithJWTSecret(t) + + cfg, err := Load() + if err != nil { + t.Fatalf("Load() error: %v", err) + } + + cfg.OIDC.Enabled = true + cfg.OIDC.ClientID = "oidc-client" + cfg.OIDC.ClientSecret = "oidc-secret" + cfg.OIDC.IssuerURL = "https://issuer.example.com" + cfg.OIDC.AuthorizeURL = "" + cfg.OIDC.TokenURL = "" + cfg.OIDC.JWKSURL = "" + cfg.OIDC.RedirectURL = "https://example.com/api/v1/auth/oauth/oidc/callback" + cfg.OIDC.FrontendRedirectURL = "/auth/oidc/callback" + cfg.OIDC.Scopes = "openid email profile" + cfg.OIDC.ValidateIDToken = true + + err = cfg.Validate() + if err != nil { + t.Fatalf("Validate() expected issuer-only OIDC config to pass with discovery fallback, got: %v", err) + } +} + func TestLoadDefaultDashboardCacheConfig(t *testing.T) { resetViperWithJWTSecret(t) diff --git a/backend/internal/domain/openai_messages_dispatch.go b/backend/internal/domain/openai_messages_dispatch.go new file mode 100644 index 0000000000000000000000000000000000000000..6b018f1c30581dad66180f6e71728a45365a38ec --- /dev/null +++ b/backend/internal/domain/openai_messages_dispatch.go @@ -0,0 +1,10 @@ +package domain + +// OpenAIMessagesDispatchModelConfig controls how Anthropic /v1/messages +// requests are mapped onto OpenAI/Codex models. +type OpenAIMessagesDispatchModelConfig struct { + OpusMappedModel string `json:"opus_mapped_model,omitempty"` + SonnetMappedModel string `json:"sonnet_mapped_model,omitempty"` + HaikuMappedModel string `json:"haiku_mapped_model,omitempty"` + ExactModelMappings map[string]string `json:"exact_model_mappings,omitempty"` +} diff --git a/backend/internal/handler/admin/account_data.go b/backend/internal/handler/admin/account_data.go index 20cc09eebcafc3dd5a786e1ea996d1948128f459..00da48212aabe095c13e013f5f3c12a84746c162 100644 --- a/backend/internal/handler/admin/account_data.go +++ b/backend/internal/handler/admin/account_data.go @@ -10,6 +10,7 @@ import ( "log/slog" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" "github.com/Wei-Shaw/sub2api/internal/pkg/openai" "github.com/Wei-Shaw/sub2api/internal/pkg/response" "github.com/Wei-Shaw/sub2api/internal/service" @@ -359,7 +360,7 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e pageSize := dataPageCap var out []service.Proxy for { - items, total, err := h.adminService.ListProxies(ctx, page, pageSize, "", "", "") + items, total, err := h.adminService.ListProxies(ctx, page, pageSize, "", "", "", "created_at", "desc") if err != nil { return nil, err } @@ -372,12 +373,12 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e return out, nil } -func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string) ([]service.Account, error) { +func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string, groupID int64, privacyMode, sortBy, sortOrder string) ([]service.Account, error) { page := 1 pageSize := dataPageCap var out []service.Account for { - items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, 0, "") + items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder) if err != nil { return nil, err } @@ -409,11 +410,28 @@ func (h *AccountHandler) resolveExportAccounts(ctx context.Context, ids []int64, platform := c.Query("platform") accountType := c.Query("type") status := c.Query("status") + privacyMode := strings.TrimSpace(c.Query("privacy_mode")) search := strings.TrimSpace(c.Query("search")) + sortBy := c.DefaultQuery("sort_by", "name") + sortOrder := c.DefaultQuery("sort_order", "asc") if len(search) > 100 { search = search[:100] } - return h.listAccountsFiltered(ctx, platform, accountType, status, search) + + groupID := int64(0) + if groupIDStr := c.Query("group"); groupIDStr != "" { + if groupIDStr == accountListGroupUngroupedQueryValue { + groupID = service.AccountListGroupUngrouped + } else { + parsedGroupID, parseErr := strconv.ParseInt(groupIDStr, 10, 64) + if parseErr != nil || parsedGroupID <= 0 { + return nil, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter") + } + groupID = parsedGroupID + } + } + + return h.listAccountsFiltered(ctx, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder) } func (h *AccountHandler) resolveExportProxies(ctx context.Context, accounts []service.Account) ([]service.Proxy, error) { diff --git a/backend/internal/handler/admin/account_data_handler_test.go b/backend/internal/handler/admin/account_data_handler_test.go index 285033a17d49f199302fe8253d97fa249b2ea6e3..5793983cba35858c9c8482f430b282d37ce7150b 100644 --- a/backend/internal/handler/admin/account_data_handler_test.go +++ b/backend/internal/handler/admin/account_data_handler_test.go @@ -172,6 +172,51 @@ func TestExportDataWithoutProxies(t *testing.T) { require.Nil(t, resp.Data.Accounts[0].ProxyKey) } +func TestExportDataPassesAccountFiltersAndSort(t *testing.T) { + router, adminSvc := setupAccountDataRouter() + adminSvc.accounts = []service.Account{ + {ID: 1, Name: "acc-1", Status: service.StatusActive}, + } + + rec := httptest.NewRecorder() + req := httptest.NewRequest( + http.MethodGet, + "/api/v1/admin/accounts/data?platform=openai&type=oauth&status=active&group=12&privacy_mode=blocked&search=keyword&sort_by=priority&sort_order=desc", + nil, + ) + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + require.Equal(t, 1, adminSvc.lastListAccounts.calls) + require.Equal(t, "openai", adminSvc.lastListAccounts.platform) + require.Equal(t, "oauth", adminSvc.lastListAccounts.accountType) + require.Equal(t, "active", adminSvc.lastListAccounts.status) + require.Equal(t, int64(12), adminSvc.lastListAccounts.groupID) + require.Equal(t, "blocked", adminSvc.lastListAccounts.privacyMode) + require.Equal(t, "keyword", adminSvc.lastListAccounts.search) + require.Equal(t, "priority", adminSvc.lastListAccounts.sortBy) + require.Equal(t, "desc", adminSvc.lastListAccounts.sortOrder) +} + +func TestExportDataSelectedIDsOverrideFilters(t *testing.T) { + router, adminSvc := setupAccountDataRouter() + + rec := httptest.NewRecorder() + req := httptest.NewRequest( + http.MethodGet, + "/api/v1/admin/accounts/data?ids=1,2&platform=openai&search=keyword&sort_by=priority&sort_order=desc", + nil, + ) + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + var resp dataResponse + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.Equal(t, 0, resp.Code) + require.Len(t, resp.Data.Accounts, 2) + require.Equal(t, 0, adminSvc.lastListAccounts.calls) +} + func TestImportDataReusesProxyAndSkipsDefaultGroup(t *testing.T) { router, adminSvc := setupAccountDataRouter() diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 9aed64d592b2beaf809fea7fcd55da49048fb488..9e985a797bd985036d77ecf63e1e59aea77daf6d 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -221,6 +221,8 @@ func (h *AccountHandler) List(c *gin.Context) { status := c.Query("status") search := c.Query("search") privacyMode := strings.TrimSpace(c.Query("privacy_mode")) + sortBy := c.DefaultQuery("sort_by", "name") + sortOrder := c.DefaultQuery("sort_order", "asc") // 标准化和验证 search 参数 search = strings.TrimSpace(search) if len(search) > 100 { @@ -246,7 +248,7 @@ func (h *AccountHandler) List(c *gin.Context) { } } - accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode) + accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return @@ -2029,7 +2031,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) { accounts := make([]*service.Account, 0) if len(req.AccountIDs) == 0 { - allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, "") + allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, "", "name", "asc") if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/handler/admin/admin_service_stub_test.go b/backend/internal/handler/admin/admin_service_stub_test.go index 60d68913e835232ab4ae5c3bd00d4e380047735a..6d1ef1b6b82426d8d2679808eb16073410cec2e2 100644 --- a/backend/internal/handler/admin/admin_service_stub_test.go +++ b/backend/internal/handler/admin/admin_service_stub_test.go @@ -31,6 +31,33 @@ type stubAdminService struct { platform string groupIDs []int64 } + lastListAccounts struct { + platform string + accountType string + status string + search string + groupID int64 + privacyMode string + sortBy string + sortOrder string + calls int + } + lastListProxies struct { + protocol string + status string + search string + sortBy string + sortOrder string + calls int + } + lastListRedeemCodes struct { + codeType string + status string + search string + sortBy string + sortOrder string + calls int + } mu sync.Mutex } @@ -99,7 +126,7 @@ func newStubAdminService() *stubAdminService { } } -func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters) ([]service.User, int64, error) { +func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters, sortBy, sortOrder string) ([]service.User, int64, error) { return s.users, int64(len(s.users)), nil } @@ -132,7 +159,7 @@ func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64, return &user, nil } -func (s *stubAdminService) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]service.APIKey, int64, error) { +func (s *stubAdminService) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]service.APIKey, int64, error) { return s.apiKeys, int64(len(s.apiKeys)), nil } @@ -140,7 +167,7 @@ func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64, return map[string]any{"user_id": userID}, nil } -func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]service.Group, int64, error) { +func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) { return s.groups, int64(len(s.groups)), nil } @@ -187,7 +214,16 @@ func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int return nil } -func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, int64, error) { +func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]service.Account, int64, error) { + s.lastListAccounts.platform = platform + s.lastListAccounts.accountType = accountType + s.lastListAccounts.status = status + s.lastListAccounts.search = search + s.lastListAccounts.groupID = groupID + s.lastListAccounts.privacyMode = privacyMode + s.lastListAccounts.sortBy = sortBy + s.lastListAccounts.sortOrder = sortOrder + s.lastListAccounts.calls++ return s.accounts, int64(len(s.accounts)), nil } @@ -261,7 +297,13 @@ func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAcc return s.checkMixedErr } -func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]service.Proxy, int64, error) { +func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]service.Proxy, int64, error) { + s.lastListProxies.protocol = protocol + s.lastListProxies.status = status + s.lastListProxies.search = search + s.lastListProxies.sortBy = sortBy + s.lastListProxies.sortOrder = sortOrder + s.lastListProxies.calls++ search = strings.TrimSpace(strings.ToLower(search)) filtered := make([]service.Proxy, 0, len(s.proxies)) for _, proxy := range s.proxies { @@ -283,7 +325,7 @@ func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, return filtered, int64(len(filtered)), nil } -func (s *stubAdminService) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]service.ProxyWithAccountCount, int64, error) { +func (s *stubAdminService) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]service.ProxyWithAccountCount, int64, error) { return s.proxyCounts, int64(len(s.proxyCounts)), nil } @@ -384,7 +426,13 @@ func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*se }, nil } -func (s *stubAdminService) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]service.RedeemCode, int64, error) { +func (s *stubAdminService) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]service.RedeemCode, int64, error) { + s.lastListRedeemCodes.codeType = codeType + s.lastListRedeemCodes.status = status + s.lastListRedeemCodes.search = search + s.lastListRedeemCodes.sortBy = sortBy + s.lastListRedeemCodes.sortOrder = sortOrder + s.lastListRedeemCodes.calls++ return s.redeems, int64(len(s.redeems)), nil } diff --git a/backend/internal/handler/admin/announcement_handler.go b/backend/internal/handler/admin/announcement_handler.go index d1312bc0c702761eceb14b2635e66252bb2dbbe8..d3b9d173731c4b093b770d55e6127ab803e93915 100644 --- a/backend/internal/handler/admin/announcement_handler.go +++ b/backend/internal/handler/admin/announcement_handler.go @@ -52,13 +52,17 @@ func (h *AnnouncementHandler) List(c *gin.Context) { page, pageSize := response.ParsePagination(c) status := strings.TrimSpace(c.Query("status")) search := strings.TrimSpace(c.Query("search")) + sortBy := c.DefaultQuery("sort_by", "created_at") + sortOrder := c.DefaultQuery("sort_order", "desc") if len(search) > 200 { search = search[:200] } params := pagination.PaginationParams{ - Page: page, - PageSize: pageSize, + Page: page, + PageSize: pageSize, + SortBy: sortBy, + SortOrder: sortOrder, } items, paginationResult, err := h.announcementService.List( @@ -227,8 +231,10 @@ func (h *AnnouncementHandler) ListReadStatus(c *gin.Context) { page, pageSize := response.ParsePagination(c) params := pagination.PaginationParams{ - Page: page, - PageSize: pageSize, + Page: page, + PageSize: pageSize, + SortBy: c.DefaultQuery("sort_by", "email"), + SortOrder: c.DefaultQuery("sort_order", "asc"), } search := strings.TrimSpace(c.Query("search")) if len(search) > 200 { diff --git a/backend/internal/handler/admin/announcement_handler_sort_test.go b/backend/internal/handler/admin/announcement_handler_sort_test.go new file mode 100644 index 0000000000000000000000000000000000000000..545e619e438ee18d3a5c364727cc9a333c7e1ea4 --- /dev/null +++ b/backend/internal/handler/admin/announcement_handler_sort_test.go @@ -0,0 +1,138 @@ +package admin + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" +) + +type announcementRepoCapture struct { + service.AnnouncementRepository + listParams pagination.PaginationParams +} + +func (r *announcementRepoCapture) List(ctx context.Context, params pagination.PaginationParams, filters service.AnnouncementListFilters) ([]service.Announcement, *pagination.PaginationResult, error) { + r.listParams = params + return []service.Announcement{}, &pagination.PaginationResult{ + Total: 0, + Page: params.Page, + PageSize: params.PageSize, + Pages: 0, + }, nil +} + +func (r *announcementRepoCapture) GetByID(ctx context.Context, id int64) (*service.Announcement, error) { + return &service.Announcement{ + ID: id, + Title: "announcement", + Content: "content", + Status: service.AnnouncementStatusActive, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, nil +} + +type announcementUserRepoCapture struct { + service.UserRepository + listParams pagination.PaginationParams +} + +func (r *announcementUserRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters service.UserListFilters) ([]service.User, *pagination.PaginationResult, error) { + r.listParams = params + return []service.User{}, &pagination.PaginationResult{ + Total: 0, + Page: params.Page, + PageSize: params.PageSize, + Pages: 0, + }, nil +} + +type announcementReadRepoCapture struct { + service.AnnouncementReadRepository +} + +func (r *announcementReadRepoCapture) GetReadMapByUsers(ctx context.Context, announcementID int64, userIDs []int64) (map[int64]time.Time, error) { + return map[int64]time.Time{}, nil +} + +type announcementUserSubRepoCapture struct { + service.UserSubscriptionRepository +} + +func newAnnouncementSortTestRouter(announcementRepo *announcementRepoCapture, userRepo *announcementUserRepoCapture) *gin.Engine { + gin.SetMode(gin.TestMode) + svc := service.NewAnnouncementService( + announcementRepo, + &announcementReadRepoCapture{}, + userRepo, + &announcementUserSubRepoCapture{}, + ) + handler := NewAnnouncementHandler(svc) + router := gin.New() + router.GET("/admin/announcements", handler.List) + router.GET("/admin/announcements/:id/read-status", handler.ListReadStatus) + return router +} + +func TestAdminAnnouncementListSortParams(t *testing.T) { + announcementRepo := &announcementRepoCapture{} + userRepo := &announcementUserRepoCapture{} + router := newAnnouncementSortTestRouter(announcementRepo, userRepo) + + req := httptest.NewRequest(http.MethodGet, "/admin/announcements?sort_by=title&sort_order=ASC", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "title", announcementRepo.listParams.SortBy) + require.Equal(t, "ASC", announcementRepo.listParams.SortOrder) +} + +func TestAdminAnnouncementListSortDefaults(t *testing.T) { + announcementRepo := &announcementRepoCapture{} + userRepo := &announcementUserRepoCapture{} + router := newAnnouncementSortTestRouter(announcementRepo, userRepo) + + req := httptest.NewRequest(http.MethodGet, "/admin/announcements", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "created_at", announcementRepo.listParams.SortBy) + require.Equal(t, "desc", announcementRepo.listParams.SortOrder) +} + +func TestAdminAnnouncementReadStatusSortParams(t *testing.T) { + announcementRepo := &announcementRepoCapture{} + userRepo := &announcementUserRepoCapture{} + router := newAnnouncementSortTestRouter(announcementRepo, userRepo) + + req := httptest.NewRequest(http.MethodGet, "/admin/announcements/1/read-status?sort_by=balance&sort_order=DESC", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "balance", userRepo.listParams.SortBy) + require.Equal(t, "DESC", userRepo.listParams.SortOrder) +} + +func TestAdminAnnouncementReadStatusSortDefaults(t *testing.T) { + announcementRepo := &announcementRepoCapture{} + userRepo := &announcementUserRepoCapture{} + router := newAnnouncementSortTestRouter(announcementRepo, userRepo) + + req := httptest.NewRequest(http.MethodGet, "/admin/announcements/1/read-status", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "email", userRepo.listParams.SortBy) + require.Equal(t, "asc", userRepo.listParams.SortOrder) +} diff --git a/backend/internal/handler/admin/channel_handler.go b/backend/internal/handler/admin/channel_handler.go index b503e5c36ed3639deab3953eaff5c36b1b86c224..c92b35bbffb664d724be29b03bab5e6f37156320 100644 --- a/backend/internal/handler/admin/channel_handler.go +++ b/backend/internal/handler/admin/channel_handler.go @@ -245,7 +245,12 @@ func (h *ChannelHandler) List(c *gin.Context) { search = search[:100] } - channels, pag, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{Page: page, PageSize: pageSize}, status, search) + channels, pag, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{ + Page: page, + PageSize: pageSize, + SortBy: c.DefaultQuery("sort_by", "created_at"), + SortOrder: c.DefaultQuery("sort_order", "desc"), + }, status, search) if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go index 458ed35d47752721eaa031dc726d3eae5a304a1d..cb2bd2018e5dff0cbdb23e06d308a8a5bb16e98d 100644 --- a/backend/internal/handler/admin/group_handler.go +++ b/backend/internal/handler/admin/group_handler.go @@ -105,10 +105,11 @@ type CreateGroupRequest struct { // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes []string `json:"supported_model_scopes"` // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch bool `json:"allow_messages_dispatch"` - RequireOAuthOnly bool `json:"require_oauth_only"` - RequirePrivacySet bool `json:"require_privacy_set"` - DefaultMappedModel string `json:"default_mapped_model"` + AllowMessagesDispatch bool `json:"allow_messages_dispatch"` + RequireOAuthOnly bool `json:"require_oauth_only"` + RequirePrivacySet bool `json:"require_privacy_set"` + DefaultMappedModel string `json:"default_mapped_model"` + MessagesDispatchModelConfig service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"` // 从指定分组复制账号(创建后自动绑定) CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"` } @@ -139,10 +140,11 @@ type UpdateGroupRequest struct { // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes *[]string `json:"supported_model_scopes"` // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch *bool `json:"allow_messages_dispatch"` - RequireOAuthOnly *bool `json:"require_oauth_only"` - RequirePrivacySet *bool `json:"require_privacy_set"` - DefaultMappedModel *string `json:"default_mapped_model"` + AllowMessagesDispatch *bool `json:"allow_messages_dispatch"` + RequireOAuthOnly *bool `json:"require_oauth_only"` + RequirePrivacySet *bool `json:"require_privacy_set"` + DefaultMappedModel *string `json:"default_mapped_model"` + MessagesDispatchModelConfig *service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"` // 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号) CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"` } @@ -160,6 +162,8 @@ func (h *GroupHandler) List(c *gin.Context) { search = search[:100] } isExclusiveStr := c.Query("is_exclusive") + sortBy := c.DefaultQuery("sort_by", "sort_order") + sortOrder := c.DefaultQuery("sort_order", "asc") var isExclusive *bool if isExclusiveStr != "" { @@ -167,7 +171,7 @@ func (h *GroupHandler) List(c *gin.Context) { isExclusive = &val } - groups, total, err := h.adminService.ListGroups(c.Request.Context(), page, pageSize, platform, status, search, isExclusive) + groups, total, err := h.adminService.ListGroups(c.Request.Context(), page, pageSize, platform, status, search, isExclusive, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return @@ -257,6 +261,7 @@ func (h *GroupHandler) Create(c *gin.Context) { RequireOAuthOnly: req.RequireOAuthOnly, RequirePrivacySet: req.RequirePrivacySet, DefaultMappedModel: req.DefaultMappedModel, + MessagesDispatchModelConfig: req.MessagesDispatchModelConfig, CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs, }) if err != nil { @@ -307,6 +312,7 @@ func (h *GroupHandler) Update(c *gin.Context) { RequireOAuthOnly: req.RequireOAuthOnly, RequirePrivacySet: req.RequirePrivacySet, DefaultMappedModel: req.DefaultMappedModel, + MessagesDispatchModelConfig: req.MessagesDispatchModelConfig, CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs, }) if err != nil { diff --git a/backend/internal/handler/admin/payment_handler.go b/backend/internal/handler/admin/payment_handler.go new file mode 100644 index 0000000000000000000000000000000000000000..b0ed6aed8f7fca88c0ed58d7b9744958e70a6766 --- /dev/null +++ b/backend/internal/handler/admin/payment_handler.go @@ -0,0 +1,323 @@ +package admin + +import ( + "strconv" + + "github.com/Wei-Shaw/sub2api/internal/pkg/response" + "github.com/Wei-Shaw/sub2api/internal/service" + + "github.com/gin-gonic/gin" +) + +// PaymentHandler handles admin payment management. +type PaymentHandler struct { + paymentService *service.PaymentService + configService *service.PaymentConfigService +} + +// NewPaymentHandler creates a new admin PaymentHandler. +func NewPaymentHandler(paymentService *service.PaymentService, configService *service.PaymentConfigService) *PaymentHandler { + return &PaymentHandler{ + paymentService: paymentService, + configService: configService, + } +} + +// --- Dashboard --- + +// GetDashboard returns payment dashboard statistics. +// GET /api/v1/admin/payment/dashboard +func (h *PaymentHandler) GetDashboard(c *gin.Context) { + days := 30 + if d := c.Query("days"); d != "" { + if v, err := strconv.Atoi(d); err == nil && v > 0 { + days = v + } + } + stats, err := h.paymentService.GetDashboardStats(c.Request.Context(), days) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, stats) +} + +// --- Orders --- + +// ListOrders returns a paginated list of all payment orders. +// GET /api/v1/admin/payment/orders +func (h *PaymentHandler) ListOrders(c *gin.Context) { + page, pageSize := response.ParsePagination(c) + var userID int64 + if uid := c.Query("user_id"); uid != "" { + if v, err := strconv.ParseInt(uid, 10, 64); err == nil { + userID = v + } + } + orders, total, err := h.paymentService.AdminListOrders(c.Request.Context(), userID, service.OrderListParams{ + Page: page, + PageSize: pageSize, + Status: c.Query("status"), + OrderType: c.Query("order_type"), + PaymentType: c.Query("payment_type"), + Keyword: c.Query("keyword"), + }) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Paginated(c, orders, int64(total), page, pageSize) +} + +// GetOrderDetail returns detailed information about a single order. +// GET /api/v1/admin/payment/orders/:id +func (h *PaymentHandler) GetOrderDetail(c *gin.Context) { + orderID, ok := parseIDParam(c, "id") + if !ok { + return + } + order, err := h.paymentService.GetOrderByID(c.Request.Context(), orderID) + if err != nil { + response.ErrorFrom(c, err) + return + } + auditLogs, _ := h.paymentService.GetOrderAuditLogs(c.Request.Context(), orderID) + response.Success(c, gin.H{"order": order, "auditLogs": auditLogs}) +} + +// CancelOrder cancels a pending order (admin). +// POST /api/v1/admin/payment/orders/:id/cancel +func (h *PaymentHandler) CancelOrder(c *gin.Context) { + orderID, ok := parseIDParam(c, "id") + if !ok { + return + } + msg, err := h.paymentService.AdminCancelOrder(c.Request.Context(), orderID) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, gin.H{"message": msg}) +} + +// RetryFulfillment retries fulfillment for a paid order. +// POST /api/v1/admin/payment/orders/:id/retry +func (h *PaymentHandler) RetryFulfillment(c *gin.Context) { + orderID, ok := parseIDParam(c, "id") + if !ok { + return + } + if err := h.paymentService.RetryFulfillment(c.Request.Context(), orderID); err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, gin.H{"message": "fulfillment retried"}) +} + +// AdminProcessRefundRequest is the request body for admin refund processing. +type AdminProcessRefundRequest struct { + Amount float64 `json:"amount"` + Reason string `json:"reason"` + Force bool `json:"force"` + DeductBalance bool `json:"deduct_balance"` +} + +// ProcessRefund processes a refund for an order (admin). +// POST /api/v1/admin/payment/orders/:id/refund +func (h *PaymentHandler) ProcessRefund(c *gin.Context) { + orderID, ok := parseIDParam(c, "id") + if !ok { + return + } + + var req AdminProcessRefundRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + + plan, earlyResult, err := h.paymentService.PrepareRefund(c.Request.Context(), orderID, req.Amount, req.Reason, req.Force, req.DeductBalance) + if err != nil { + response.ErrorFrom(c, err) + return + } + if earlyResult != nil { + response.Success(c, earlyResult) + return + } + + result, err := h.paymentService.ExecuteRefund(c.Request.Context(), plan) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, result) +} + +// --- Subscription Plans --- + +// ListPlans returns all subscription plans. +// GET /api/v1/admin/payment/plans +func (h *PaymentHandler) ListPlans(c *gin.Context) { + plans, err := h.configService.ListPlans(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, plans) +} + +// CreatePlan creates a new subscription plan. +// POST /api/v1/admin/payment/plans +func (h *PaymentHandler) CreatePlan(c *gin.Context) { + var req service.CreatePlanRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + plan, err := h.configService.CreatePlan(c.Request.Context(), req) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Created(c, plan) +} + +// UpdatePlan updates an existing subscription plan. +// PUT /api/v1/admin/payment/plans/:id +func (h *PaymentHandler) UpdatePlan(c *gin.Context) { + id, ok := parseIDParam(c, "id") + if !ok { + return + } + var req service.UpdatePlanRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + plan, err := h.configService.UpdatePlan(c.Request.Context(), id, req) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, plan) +} + +// DeletePlan deletes a subscription plan. +// DELETE /api/v1/admin/payment/plans/:id +func (h *PaymentHandler) DeletePlan(c *gin.Context) { + id, ok := parseIDParam(c, "id") + if !ok { + return + } + if err := h.configService.DeletePlan(c.Request.Context(), id); err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, gin.H{"message": "deleted"}) +} + +// --- Provider Instances --- + +// ListProviders returns all payment provider instances. +// GET /api/v1/admin/payment/providers +func (h *PaymentHandler) ListProviders(c *gin.Context) { + providers, err := h.configService.ListProviderInstancesWithConfig(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, providers) +} + +// CreateProvider creates a new payment provider instance. +// POST /api/v1/admin/payment/providers +func (h *PaymentHandler) CreateProvider(c *gin.Context) { + var req service.CreateProviderInstanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + inst, err := h.configService.CreateProviderInstance(c.Request.Context(), req) + if err != nil { + response.ErrorFrom(c, err) + return + } + h.paymentService.RefreshProviders(c.Request.Context()) + response.Created(c, inst) +} + +// UpdateProvider updates an existing payment provider instance. +// PUT /api/v1/admin/payment/providers/:id +func (h *PaymentHandler) UpdateProvider(c *gin.Context) { + id, ok := parseIDParam(c, "id") + if !ok { + return + } + var req service.UpdateProviderInstanceRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + inst, err := h.configService.UpdateProviderInstance(c.Request.Context(), id, req) + if err != nil { + response.ErrorFrom(c, err) + return + } + h.paymentService.RefreshProviders(c.Request.Context()) + response.Success(c, inst) +} + +// DeleteProvider deletes a payment provider instance. +// DELETE /api/v1/admin/payment/providers/:id +func (h *PaymentHandler) DeleteProvider(c *gin.Context) { + id, ok := parseIDParam(c, "id") + if !ok { + return + } + if err := h.configService.DeleteProviderInstance(c.Request.Context(), id); err != nil { + response.ErrorFrom(c, err) + return + } + h.paymentService.RefreshProviders(c.Request.Context()) + response.Success(c, gin.H{"message": "deleted"}) +} + +// parseIDParam parses an int64 path parameter. +// Returns the parsed ID and true on success; on failure it writes a BadRequest response and returns false. +func parseIDParam(c *gin.Context, paramName string) (int64, bool) { + id, err := strconv.ParseInt(c.Param(paramName), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid "+paramName) + return 0, false + } + return id, true +} + +// --- Config --- + +// GetConfig returns the payment configuration (admin view). +// GET /api/v1/admin/payment/config +func (h *PaymentHandler) GetConfig(c *gin.Context) { + cfg, err := h.configService.GetPaymentConfig(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, cfg) +} + +// UpdateConfig updates the payment configuration. +// PUT /api/v1/admin/payment/config +func (h *PaymentHandler) UpdateConfig(c *gin.Context) { + var req service.UpdatePaymentConfigRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + if err := h.configService.UpdatePaymentConfig(c.Request.Context(), req); err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, gin.H{"message": "updated"}) +} diff --git a/backend/internal/handler/admin/promo_handler.go b/backend/internal/handler/admin/promo_handler.go index 3eafa3801a7a6ab480fb9d20ecd1b67a37406224..77d5f171650ce9834b599e7d631386e417a43902 100644 --- a/backend/internal/handler/admin/promo_handler.go +++ b/backend/internal/handler/admin/promo_handler.go @@ -55,8 +55,10 @@ func (h *PromoHandler) List(c *gin.Context) { } params := pagination.PaginationParams{ - Page: page, - PageSize: pageSize, + Page: page, + PageSize: pageSize, + SortBy: c.DefaultQuery("sort_by", "created_at"), + SortOrder: c.DefaultQuery("sort_order", "desc"), } codes, paginationResult, err := h.promoService.List(c.Request.Context(), params, status, search) diff --git a/backend/internal/handler/admin/proxy_data.go b/backend/internal/handler/admin/proxy_data.go index 72ecd6c13129629b7362f256e7dd4dbc00a8da4d..8149ce3b3ca9a4be77c21aaa96feff36d0b719f4 100644 --- a/backend/internal/handler/admin/proxy_data.go +++ b/backend/internal/handler/admin/proxy_data.go @@ -33,11 +33,13 @@ func (h *ProxyHandler) ExportData(c *gin.Context) { protocol := c.Query("protocol") status := c.Query("status") search := strings.TrimSpace(c.Query("search")) + sortBy := c.DefaultQuery("sort_by", "id") + sortOrder := c.DefaultQuery("sort_order", "desc") if len(search) > 100 { search = search[:100] } - proxies, err = h.listProxiesFiltered(ctx, protocol, status, search) + proxies, err = h.listProxiesFiltered(ctx, protocol, status, search, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return @@ -89,7 +91,7 @@ func (h *ProxyHandler) ImportData(c *gin.Context) { ctx := c.Request.Context() result := DataImportResult{} - existingProxies, err := h.listProxiesFiltered(ctx, "", "", "") + existingProxies, err := h.listProxiesFiltered(ctx, "", "", "", "id", "desc") if err != nil { response.ErrorFrom(c, err) return @@ -220,18 +222,33 @@ func parseProxyIDs(c *gin.Context) ([]int64, error) { return ids, nil } -func (h *ProxyHandler) listProxiesFiltered(ctx context.Context, protocol, status, search string) ([]service.Proxy, error) { +func (h *ProxyHandler) listProxiesFiltered(ctx context.Context, protocol, status, search, sortBy, sortOrder string) ([]service.Proxy, error) { page := 1 pageSize := dataPageCap var out []service.Proxy + sortBy = strings.TrimSpace(sortBy) + useAccountCountSort := strings.EqualFold(sortBy, "account_count") for { - items, total, err := h.adminService.ListProxies(ctx, page, pageSize, protocol, status, search) - if err != nil { - return nil, err - } - out = append(out, items...) - if len(out) >= int(total) || len(items) == 0 { - break + if useAccountCountSort { + items, total, err := h.adminService.ListProxiesWithAccountCount(ctx, page, pageSize, protocol, status, search, sortBy, sortOrder) + if err != nil { + return nil, err + } + for i := range items { + out = append(out, items[i].Proxy) + } + if len(out) >= int(total) || len(items) == 0 { + break + } + } else { + items, total, err := h.adminService.ListProxies(ctx, page, pageSize, protocol, status, search, sortBy, sortOrder) + if err != nil { + return nil, err + } + out = append(out, items...) + if len(out) >= int(total) || len(items) == 0 { + break + } } page++ } diff --git a/backend/internal/handler/admin/proxy_data_handler_test.go b/backend/internal/handler/admin/proxy_data_handler_test.go index 803f9b6135f88419da10b60b9a1c8043698f9b28..8cd035ed3aff4b051b89e70f0eb358e55c9056f2 100644 --- a/backend/internal/handler/admin/proxy_data_handler_test.go +++ b/backend/internal/handler/admin/proxy_data_handler_test.go @@ -74,6 +74,10 @@ func TestProxyExportDataRespectsFilters(t *testing.T) { require.Len(t, resp.Data.Proxies, 1) require.Len(t, resp.Data.Accounts, 0) require.Equal(t, "https", resp.Data.Proxies[0].Protocol) + require.Equal(t, 1, adminSvc.lastListProxies.calls) + require.Equal(t, "https", adminSvc.lastListProxies.protocol) + require.Equal(t, "id", adminSvc.lastListProxies.sortBy) + require.Equal(t, "desc", adminSvc.lastListProxies.sortOrder) } func TestProxyExportDataWithSelectedIDs(t *testing.T) { @@ -113,6 +117,96 @@ func TestProxyExportDataWithSelectedIDs(t *testing.T) { require.Len(t, resp.Data.Proxies, 1) require.Equal(t, "https", resp.Data.Proxies[0].Protocol) require.Equal(t, "10.0.0.2", resp.Data.Proxies[0].Host) + require.Equal(t, 0, adminSvc.lastListProxies.calls) +} + +func TestProxyExportDataPassesSortParams(t *testing.T) { + router, adminSvc := setupProxyDataRouter() + + adminSvc.proxies = []service.Proxy{ + { + ID: 1, + Name: "proxy-a", + Protocol: "http", + Host: "127.0.0.1", + Port: 8080, + Username: "user", + Password: "pass", + Status: service.StatusActive, + }, + } + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/data?protocol=http&status=active&search=proxy&sort_by=name&sort_order=asc", nil) + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + require.Equal(t, 1, adminSvc.lastListProxies.calls) + require.Equal(t, "http", adminSvc.lastListProxies.protocol) + require.Equal(t, "active", adminSvc.lastListProxies.status) + require.Equal(t, "proxy", adminSvc.lastListProxies.search) + require.Equal(t, "name", adminSvc.lastListProxies.sortBy) + require.Equal(t, "asc", adminSvc.lastListProxies.sortOrder) +} + +func TestProxyExportDataSortByAccountCountUsesAccountCountListing(t *testing.T) { + router, adminSvc := setupProxyDataRouter() + + adminSvc.proxies = []service.Proxy{ + { + ID: 1, + Name: "proxy-id-1", + Protocol: "http", + Host: "127.0.0.1", + Port: 8080, + Status: service.StatusActive, + }, + { + ID: 2, + Name: "proxy-id-2", + Protocol: "http", + Host: "127.0.0.2", + Port: 8081, + Status: service.StatusActive, + }, + } + adminSvc.proxyCounts = []service.ProxyWithAccountCount{ + { + Proxy: service.Proxy{ + ID: 2, + Name: "proxy-count-high", + Protocol: "http", + Host: "127.0.0.2", + Port: 8081, + Status: service.StatusActive, + }, + AccountCount: 9, + }, + { + Proxy: service.Proxy{ + ID: 1, + Name: "proxy-count-low", + Protocol: "http", + Host: "127.0.0.1", + Port: 8080, + Status: service.StatusActive, + }, + AccountCount: 1, + }, + } + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/data?sort_by=account_count&sort_order=desc", nil) + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + var resp proxyDataResponse + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.Equal(t, 0, resp.Code) + require.Len(t, resp.Data.Proxies, 2) + require.Equal(t, "proxy-count-high", resp.Data.Proxies[0].Name) + require.Equal(t, "proxy-count-low", resp.Data.Proxies[1].Name) + require.Equal(t, 0, adminSvc.lastListProxies.calls) } func TestProxyImportDataReusesAndTriggersLatencyProbe(t *testing.T) { diff --git a/backend/internal/handler/admin/proxy_handler.go b/backend/internal/handler/admin/proxy_handler.go index e8ae0ce2d0e125e127f0bf7aa4062216351e497c..f97fcb0a71a16daad1d99168f2d9ae5d55d9271e 100644 --- a/backend/internal/handler/admin/proxy_handler.go +++ b/backend/internal/handler/admin/proxy_handler.go @@ -52,13 +52,15 @@ func (h *ProxyHandler) List(c *gin.Context) { protocol := c.Query("protocol") status := c.Query("status") search := c.Query("search") + sortBy := c.DefaultQuery("sort_by", "id") + sortOrder := c.DefaultQuery("sort_order", "desc") // 标准化和验证 search 参数 search = strings.TrimSpace(search) if len(search) > 100 { search = search[:100] } - proxies, total, err := h.adminService.ListProxiesWithAccountCount(c.Request.Context(), page, pageSize, protocol, status, search) + proxies, total, err := h.adminService.ListProxiesWithAccountCount(c.Request.Context(), page, pageSize, protocol, status, search, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/handler/admin/redeem_export_handler_test.go b/backend/internal/handler/admin/redeem_export_handler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9983fe319ca18dbec8d2070f02a813b35ab4fb14 --- /dev/null +++ b/backend/internal/handler/admin/redeem_export_handler_test.go @@ -0,0 +1,49 @@ +package admin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" +) + +func setupRedeemExportRouter() (*gin.Engine, *stubAdminService) { + gin.SetMode(gin.TestMode) + router := gin.New() + adminSvc := newStubAdminService() + + h := NewRedeemHandler(adminSvc, nil) + router.GET("/api/v1/admin/redeem-codes/export", h.Export) + return router, adminSvc +} + +func TestRedeemExportPassesSearchAndSort(t *testing.T) { + router, adminSvc := setupRedeemExportRouter() + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/export?type=balance&status=unused&search=ABC&sort_by=value&sort_order=asc", nil) + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + require.Equal(t, 1, adminSvc.lastListRedeemCodes.calls) + require.Equal(t, "balance", adminSvc.lastListRedeemCodes.codeType) + require.Equal(t, "unused", adminSvc.lastListRedeemCodes.status) + require.Equal(t, "ABC", adminSvc.lastListRedeemCodes.search) + require.Equal(t, "value", adminSvc.lastListRedeemCodes.sortBy) + require.Equal(t, "asc", adminSvc.lastListRedeemCodes.sortOrder) +} + +func TestRedeemExportSortDefaults(t *testing.T) { + router, adminSvc := setupRedeemExportRouter() + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/export", nil) + router.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + + require.Equal(t, 1, adminSvc.lastListRedeemCodes.calls) + require.Equal(t, "id", adminSvc.lastListRedeemCodes.sortBy) + require.Equal(t, "desc", adminSvc.lastListRedeemCodes.sortOrder) +} diff --git a/backend/internal/handler/admin/redeem_handler.go b/backend/internal/handler/admin/redeem_handler.go index c494e5fb8b5df25dbc1197a55ceed141088c4e19..24365f3da41c2176d7e1e120eaa02d70a846fbaa 100644 --- a/backend/internal/handler/admin/redeem_handler.go +++ b/backend/internal/handler/admin/redeem_handler.go @@ -59,13 +59,15 @@ func (h *RedeemHandler) List(c *gin.Context) { codeType := c.Query("type") status := c.Query("status") search := c.Query("search") + sortBy := c.DefaultQuery("sort_by", "id") + sortOrder := c.DefaultQuery("sort_order", "desc") // 标准化和验证 search 参数 search = strings.TrimSpace(search) if len(search) > 100 { search = search[:100] } - codes, total, err := h.adminService.ListRedeemCodes(c.Request.Context(), page, pageSize, codeType, status, search) + codes, total, err := h.adminService.ListRedeemCodes(c.Request.Context(), page, pageSize, codeType, status, search, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return @@ -300,9 +302,15 @@ func (h *RedeemHandler) GetStats(c *gin.Context) { func (h *RedeemHandler) Export(c *gin.Context) { codeType := c.Query("type") status := c.Query("status") + search := strings.TrimSpace(c.Query("search")) + sortBy := c.DefaultQuery("sort_by", "id") + sortOrder := c.DefaultQuery("sort_order", "desc") + if len(search) > 100 { + search = search[:100] + } // Get all codes without pagination (use large page size) - codes, _, err := h.adminService.ListRedeemCodes(c.Request.Context(), 1, 10000, codeType, status, "") + codes, _, err := h.adminService.ListRedeemCodes(c.Request.Context(), 1, 10000, codeType, status, search, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index 4cbe5188ec27cb349675b0c107170a70ed11202f..ba7511315c65025dcd657c8a4748a49992347a68 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -35,21 +35,34 @@ func generateMenuItemID() (string, error) { return hex.EncodeToString(b), nil } +func scopesContainOpenID(scopes string) bool { + for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) { + if scope == "openid" { + return true + } + } + return false +} + // SettingHandler 系统设置处理器 type SettingHandler struct { - settingService *service.SettingService - emailService *service.EmailService - turnstileService *service.TurnstileService - opsService *service.OpsService + settingService *service.SettingService + emailService *service.EmailService + turnstileService *service.TurnstileService + opsService *service.OpsService + paymentConfigService *service.PaymentConfigService + paymentService *service.PaymentService } // NewSettingHandler 创建系统设置处理器 -func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService) *SettingHandler { +func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService, paymentConfigService *service.PaymentConfigService, paymentService *service.PaymentService) *SettingHandler { return &SettingHandler{ - settingService: settingService, - emailService: emailService, - turnstileService: turnstileService, - opsService: opsService, + settingService: settingService, + emailService: emailService, + turnstileService: turnstileService, + opsService: opsService, + paymentConfigService: paymentConfigService, + paymentService: paymentService, } } @@ -72,6 +85,15 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { }) } + // Load payment config + var paymentCfg *service.PaymentConfig + if h.paymentConfigService != nil { + paymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context()) + } + if paymentCfg == nil { + paymentCfg = &service.PaymentConfig{} + } + response.Success(c, dto.SystemSettings{ RegistrationEnabled: settings.RegistrationEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled, @@ -96,6 +118,28 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { LinuxDoConnectClientID: settings.LinuxDoConnectClientID, LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured, LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL, + OIDCConnectEnabled: settings.OIDCConnectEnabled, + OIDCConnectProviderName: settings.OIDCConnectProviderName, + OIDCConnectClientID: settings.OIDCConnectClientID, + OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured, + OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL, + OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL, + OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL, + OIDCConnectTokenURL: settings.OIDCConnectTokenURL, + OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL, + OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL, + OIDCConnectScopes: settings.OIDCConnectScopes, + OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL, + OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL, + OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod, + OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE, + OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken, + OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs, + OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds, + OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified, + OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath, + OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath, + OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath, SiteName: settings.SiteName, SiteLogo: settings.SiteLogo, SiteSubtitle: settings.SiteSubtitle, @@ -106,6 +150,8 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { HideCcsImportButton: settings.HideCcsImportButton, PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled, PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL, + TableDefaultPageSize: settings.TableDefaultPageSize, + TablePageSizeOptions: settings.TablePageSizeOptions, CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems), CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints), DefaultConcurrency: settings.DefaultConcurrency, @@ -129,6 +175,24 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { EnableFingerprintUnification: settings.EnableFingerprintUnification, EnableMetadataPassthrough: settings.EnableMetadataPassthrough, EnableCCHSigning: settings.EnableCCHSigning, + PaymentEnabled: paymentCfg.Enabled, + PaymentMinAmount: paymentCfg.MinAmount, + PaymentMaxAmount: paymentCfg.MaxAmount, + PaymentDailyLimit: paymentCfg.DailyLimit, + PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin, + PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders, + PaymentEnabledTypes: paymentCfg.EnabledTypes, + PaymentBalanceDisabled: paymentCfg.BalanceDisabled, + PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy, + PaymentProductNamePrefix: paymentCfg.ProductNamePrefix, + PaymentProductNameSuffix: paymentCfg.ProductNameSuffix, + PaymentHelpImageURL: paymentCfg.HelpImageURL, + PaymentHelpText: paymentCfg.HelpText, + PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled, + PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax, + PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow, + PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit, + PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode, }) } @@ -164,6 +228,30 @@ type UpdateSettingsRequest struct { LinuxDoConnectClientSecret string `json:"linuxdo_connect_client_secret"` LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"` + // Generic OIDC OAuth 登录 + OIDCConnectEnabled bool `json:"oidc_connect_enabled"` + OIDCConnectProviderName string `json:"oidc_connect_provider_name"` + OIDCConnectClientID string `json:"oidc_connect_client_id"` + OIDCConnectClientSecret string `json:"oidc_connect_client_secret"` + OIDCConnectIssuerURL string `json:"oidc_connect_issuer_url"` + OIDCConnectDiscoveryURL string `json:"oidc_connect_discovery_url"` + OIDCConnectAuthorizeURL string `json:"oidc_connect_authorize_url"` + OIDCConnectTokenURL string `json:"oidc_connect_token_url"` + OIDCConnectUserInfoURL string `json:"oidc_connect_userinfo_url"` + OIDCConnectJWKSURL string `json:"oidc_connect_jwks_url"` + OIDCConnectScopes string `json:"oidc_connect_scopes"` + OIDCConnectRedirectURL string `json:"oidc_connect_redirect_url"` + OIDCConnectFrontendRedirectURL string `json:"oidc_connect_frontend_redirect_url"` + OIDCConnectTokenAuthMethod string `json:"oidc_connect_token_auth_method"` + OIDCConnectUsePKCE bool `json:"oidc_connect_use_pkce"` + OIDCConnectValidateIDToken bool `json:"oidc_connect_validate_id_token"` + OIDCConnectAllowedSigningAlgs string `json:"oidc_connect_allowed_signing_algs"` + OIDCConnectClockSkewSeconds int `json:"oidc_connect_clock_skew_seconds"` + OIDCConnectRequireEmailVerified bool `json:"oidc_connect_require_email_verified"` + OIDCConnectUserInfoEmailPath string `json:"oidc_connect_userinfo_email_path"` + OIDCConnectUserInfoIDPath string `json:"oidc_connect_userinfo_id_path"` + OIDCConnectUserInfoUsernamePath string `json:"oidc_connect_userinfo_username_path"` + // OEM设置 SiteName string `json:"site_name"` SiteLogo string `json:"site_logo"` @@ -175,6 +263,8 @@ type UpdateSettingsRequest struct { HideCcsImportButton bool `json:"hide_ccs_import_button"` PurchaseSubscriptionEnabled *bool `json:"purchase_subscription_enabled"` PurchaseSubscriptionURL *string `json:"purchase_subscription_url"` + TableDefaultPageSize int `json:"table_default_page_size"` + TablePageSizeOptions []int `json:"table_page_size_options"` CustomMenuItems *[]dto.CustomMenuItem `json:"custom_menu_items"` CustomEndpoints *[]dto.CustomEndpoint `json:"custom_endpoints"` @@ -213,6 +303,28 @@ type UpdateSettingsRequest struct { EnableFingerprintUnification *bool `json:"enable_fingerprint_unification"` EnableMetadataPassthrough *bool `json:"enable_metadata_passthrough"` EnableCCHSigning *bool `json:"enable_cch_signing"` + + // Payment configuration (integrated into settings, full replace) + PaymentEnabled *bool `json:"payment_enabled"` + PaymentMinAmount *float64 `json:"payment_min_amount"` + PaymentMaxAmount *float64 `json:"payment_max_amount"` + PaymentDailyLimit *float64 `json:"payment_daily_limit"` + PaymentOrderTimeoutMin *int `json:"payment_order_timeout_minutes"` + PaymentMaxPendingOrders *int `json:"payment_max_pending_orders"` + PaymentEnabledTypes []string `json:"payment_enabled_types"` + PaymentBalanceDisabled *bool `json:"payment_balance_disabled"` + PaymentLoadBalanceStrat *string `json:"payment_load_balance_strategy"` + PaymentProductNamePrefix *string `json:"payment_product_name_prefix"` + PaymentProductNameSuffix *string `json:"payment_product_name_suffix"` + PaymentHelpImageURL *string `json:"payment_help_image_url"` + PaymentHelpText *string `json:"payment_help_text"` + + // Cancel rate limit + PaymentCancelRateLimitEnabled *bool `json:"payment_cancel_rate_limit_enabled"` + PaymentCancelRateLimitMax *int `json:"payment_cancel_rate_limit_max"` + PaymentCancelRateLimitWindow *int `json:"payment_cancel_rate_limit_window"` + PaymentCancelRateLimitUnit *string `json:"payment_cancel_rate_limit_unit"` + PaymentCancelRateLimitMode *string `json:"payment_cancel_rate_limit_window_mode"` } // UpdateSettings 更新系统设置 @@ -237,6 +349,13 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { if req.DefaultBalance < 0 { req.DefaultBalance = 0 } + // 通用表格配置:兼容旧客户端未传字段时保留当前值。 + if req.TableDefaultPageSize <= 0 { + req.TableDefaultPageSize = previousSettings.TableDefaultPageSize + } + if req.TablePageSizeOptions == nil { + req.TablePageSizeOptions = previousSettings.TablePageSizeOptions + } req.SMTPHost = strings.TrimSpace(req.SMTPHost) req.SMTPUsername = strings.TrimSpace(req.SMTPUsername) req.SMTPPassword = strings.TrimSpace(req.SMTPPassword) @@ -324,6 +443,122 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { } } + // Generic OIDC 参数验证 + if req.OIDCConnectEnabled { + req.OIDCConnectProviderName = strings.TrimSpace(req.OIDCConnectProviderName) + req.OIDCConnectClientID = strings.TrimSpace(req.OIDCConnectClientID) + req.OIDCConnectClientSecret = strings.TrimSpace(req.OIDCConnectClientSecret) + req.OIDCConnectIssuerURL = strings.TrimSpace(req.OIDCConnectIssuerURL) + req.OIDCConnectDiscoveryURL = strings.TrimSpace(req.OIDCConnectDiscoveryURL) + req.OIDCConnectAuthorizeURL = strings.TrimSpace(req.OIDCConnectAuthorizeURL) + req.OIDCConnectTokenURL = strings.TrimSpace(req.OIDCConnectTokenURL) + req.OIDCConnectUserInfoURL = strings.TrimSpace(req.OIDCConnectUserInfoURL) + req.OIDCConnectJWKSURL = strings.TrimSpace(req.OIDCConnectJWKSURL) + req.OIDCConnectScopes = strings.TrimSpace(req.OIDCConnectScopes) + req.OIDCConnectRedirectURL = strings.TrimSpace(req.OIDCConnectRedirectURL) + req.OIDCConnectFrontendRedirectURL = strings.TrimSpace(req.OIDCConnectFrontendRedirectURL) + req.OIDCConnectTokenAuthMethod = strings.ToLower(strings.TrimSpace(req.OIDCConnectTokenAuthMethod)) + req.OIDCConnectAllowedSigningAlgs = strings.TrimSpace(req.OIDCConnectAllowedSigningAlgs) + req.OIDCConnectUserInfoEmailPath = strings.TrimSpace(req.OIDCConnectUserInfoEmailPath) + req.OIDCConnectUserInfoIDPath = strings.TrimSpace(req.OIDCConnectUserInfoIDPath) + req.OIDCConnectUserInfoUsernamePath = strings.TrimSpace(req.OIDCConnectUserInfoUsernamePath) + + if req.OIDCConnectProviderName == "" { + req.OIDCConnectProviderName = "OIDC" + } + if req.OIDCConnectClientID == "" { + response.BadRequest(c, "OIDC Client ID is required when enabled") + return + } + if req.OIDCConnectIssuerURL == "" { + response.BadRequest(c, "OIDC Issuer URL is required when enabled") + return + } + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectIssuerURL); err != nil { + response.BadRequest(c, "OIDC Issuer URL must be an absolute http(s) URL") + return + } + if req.OIDCConnectDiscoveryURL != "" { + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectDiscoveryURL); err != nil { + response.BadRequest(c, "OIDC Discovery URL must be an absolute http(s) URL") + return + } + } + if req.OIDCConnectAuthorizeURL != "" { + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectAuthorizeURL); err != nil { + response.BadRequest(c, "OIDC Authorize URL must be an absolute http(s) URL") + return + } + } + if req.OIDCConnectTokenURL != "" { + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectTokenURL); err != nil { + response.BadRequest(c, "OIDC Token URL must be an absolute http(s) URL") + return + } + } + if req.OIDCConnectUserInfoURL != "" { + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectUserInfoURL); err != nil { + response.BadRequest(c, "OIDC UserInfo URL must be an absolute http(s) URL") + return + } + } + if req.OIDCConnectRedirectURL == "" { + response.BadRequest(c, "OIDC Redirect URL is required when enabled") + return + } + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectRedirectURL); err != nil { + response.BadRequest(c, "OIDC Redirect URL must be an absolute http(s) URL") + return + } + if req.OIDCConnectFrontendRedirectURL == "" { + response.BadRequest(c, "OIDC Frontend Redirect URL is required when enabled") + return + } + if err := config.ValidateFrontendRedirectURL(req.OIDCConnectFrontendRedirectURL); err != nil { + response.BadRequest(c, "OIDC Frontend Redirect URL is invalid") + return + } + if !scopesContainOpenID(req.OIDCConnectScopes) { + response.BadRequest(c, "OIDC scopes must contain openid") + return + } + switch req.OIDCConnectTokenAuthMethod { + case "", "client_secret_post", "client_secret_basic", "none": + default: + response.BadRequest(c, "OIDC Token Auth Method must be one of client_secret_post/client_secret_basic/none") + return + } + if req.OIDCConnectTokenAuthMethod == "none" && !req.OIDCConnectUsePKCE { + response.BadRequest(c, "OIDC PKCE must be enabled when token_auth_method=none") + return + } + if req.OIDCConnectClockSkewSeconds < 0 || req.OIDCConnectClockSkewSeconds > 600 { + response.BadRequest(c, "OIDC clock skew seconds must be between 0 and 600") + return + } + if req.OIDCConnectValidateIDToken { + if req.OIDCConnectAllowedSigningAlgs == "" { + response.BadRequest(c, "OIDC Allowed Signing Algs is required when validate_id_token=true") + return + } + } + if req.OIDCConnectJWKSURL != "" { + if err := config.ValidateAbsoluteHTTPURL(req.OIDCConnectJWKSURL); err != nil { + response.BadRequest(c, "OIDC JWKS URL must be an absolute http(s) URL") + return + } + } + if req.OIDCConnectTokenAuthMethod == "" || req.OIDCConnectTokenAuthMethod == "client_secret_post" || req.OIDCConnectTokenAuthMethod == "client_secret_basic" { + if req.OIDCConnectClientSecret == "" { + if previousSettings.OIDCConnectClientSecret == "" { + response.BadRequest(c, "OIDC Client Secret is required when enabled") + return + } + req.OIDCConnectClientSecret = previousSettings.OIDCConnectClientSecret + } + } + } + // “购买订阅”页面配置验证 purchaseEnabled := previousSettings.PurchaseSubscriptionEnabled if req.PurchaseSubscriptionEnabled != nil { @@ -554,6 +789,28 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { LinuxDoConnectClientID: req.LinuxDoConnectClientID, LinuxDoConnectClientSecret: req.LinuxDoConnectClientSecret, LinuxDoConnectRedirectURL: req.LinuxDoConnectRedirectURL, + OIDCConnectEnabled: req.OIDCConnectEnabled, + OIDCConnectProviderName: req.OIDCConnectProviderName, + OIDCConnectClientID: req.OIDCConnectClientID, + OIDCConnectClientSecret: req.OIDCConnectClientSecret, + OIDCConnectIssuerURL: req.OIDCConnectIssuerURL, + OIDCConnectDiscoveryURL: req.OIDCConnectDiscoveryURL, + OIDCConnectAuthorizeURL: req.OIDCConnectAuthorizeURL, + OIDCConnectTokenURL: req.OIDCConnectTokenURL, + OIDCConnectUserInfoURL: req.OIDCConnectUserInfoURL, + OIDCConnectJWKSURL: req.OIDCConnectJWKSURL, + OIDCConnectScopes: req.OIDCConnectScopes, + OIDCConnectRedirectURL: req.OIDCConnectRedirectURL, + OIDCConnectFrontendRedirectURL: req.OIDCConnectFrontendRedirectURL, + OIDCConnectTokenAuthMethod: req.OIDCConnectTokenAuthMethod, + OIDCConnectUsePKCE: req.OIDCConnectUsePKCE, + OIDCConnectValidateIDToken: req.OIDCConnectValidateIDToken, + OIDCConnectAllowedSigningAlgs: req.OIDCConnectAllowedSigningAlgs, + OIDCConnectClockSkewSeconds: req.OIDCConnectClockSkewSeconds, + OIDCConnectRequireEmailVerified: req.OIDCConnectRequireEmailVerified, + OIDCConnectUserInfoEmailPath: req.OIDCConnectUserInfoEmailPath, + OIDCConnectUserInfoIDPath: req.OIDCConnectUserInfoIDPath, + OIDCConnectUserInfoUsernamePath: req.OIDCConnectUserInfoUsernamePath, SiteName: req.SiteName, SiteLogo: req.SiteLogo, SiteSubtitle: req.SiteSubtitle, @@ -564,6 +821,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { HideCcsImportButton: req.HideCcsImportButton, PurchaseSubscriptionEnabled: purchaseEnabled, PurchaseSubscriptionURL: purchaseURL, + TableDefaultPageSize: req.TableDefaultPageSize, + TablePageSizeOptions: req.TablePageSizeOptions, CustomMenuItems: customMenuJSON, CustomEndpoints: customEndpointsJSON, DefaultConcurrency: req.DefaultConcurrency, @@ -629,6 +888,39 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { return } + // Update payment configuration (integrated into system settings). + // Skip if no payment fields were provided (prevents accidental wipe). + if h.paymentConfigService != nil && hasPaymentFields(req) { + paymentReq := service.UpdatePaymentConfigRequest{ + Enabled: req.PaymentEnabled, + MinAmount: req.PaymentMinAmount, + MaxAmount: req.PaymentMaxAmount, + DailyLimit: req.PaymentDailyLimit, + OrderTimeoutMin: req.PaymentOrderTimeoutMin, + MaxPendingOrders: req.PaymentMaxPendingOrders, + EnabledTypes: req.PaymentEnabledTypes, + BalanceDisabled: req.PaymentBalanceDisabled, + LoadBalanceStrategy: req.PaymentLoadBalanceStrat, + ProductNamePrefix: req.PaymentProductNamePrefix, + ProductNameSuffix: req.PaymentProductNameSuffix, + HelpImageURL: req.PaymentHelpImageURL, + HelpText: req.PaymentHelpText, + CancelRateLimitEnabled: req.PaymentCancelRateLimitEnabled, + CancelRateLimitMax: req.PaymentCancelRateLimitMax, + CancelRateLimitWindow: req.PaymentCancelRateLimitWindow, + CancelRateLimitUnit: req.PaymentCancelRateLimitUnit, + CancelRateLimitMode: req.PaymentCancelRateLimitMode, + } + if err := h.paymentConfigService.UpdatePaymentConfig(c.Request.Context(), paymentReq); err != nil { + response.ErrorFrom(c, err) + return + } + // Refresh in-memory provider registry so config changes take effect immediately + if h.paymentService != nil { + h.paymentService.RefreshProviders(c.Request.Context()) + } + } + h.auditSettingsUpdate(c, previousSettings, settings, req) // 重新获取设置返回 @@ -645,6 +937,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { }) } + // Reload payment config for response + var updatedPaymentCfg *service.PaymentConfig + if h.paymentConfigService != nil { + updatedPaymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context()) + } + if updatedPaymentCfg == nil { + updatedPaymentCfg = &service.PaymentConfig{} + } + response.Success(c, dto.SystemSettings{ RegistrationEnabled: updatedSettings.RegistrationEnabled, EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled, @@ -669,6 +970,28 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { LinuxDoConnectClientID: updatedSettings.LinuxDoConnectClientID, LinuxDoConnectClientSecretConfigured: updatedSettings.LinuxDoConnectClientSecretConfigured, LinuxDoConnectRedirectURL: updatedSettings.LinuxDoConnectRedirectURL, + OIDCConnectEnabled: updatedSettings.OIDCConnectEnabled, + OIDCConnectProviderName: updatedSettings.OIDCConnectProviderName, + OIDCConnectClientID: updatedSettings.OIDCConnectClientID, + OIDCConnectClientSecretConfigured: updatedSettings.OIDCConnectClientSecretConfigured, + OIDCConnectIssuerURL: updatedSettings.OIDCConnectIssuerURL, + OIDCConnectDiscoveryURL: updatedSettings.OIDCConnectDiscoveryURL, + OIDCConnectAuthorizeURL: updatedSettings.OIDCConnectAuthorizeURL, + OIDCConnectTokenURL: updatedSettings.OIDCConnectTokenURL, + OIDCConnectUserInfoURL: updatedSettings.OIDCConnectUserInfoURL, + OIDCConnectJWKSURL: updatedSettings.OIDCConnectJWKSURL, + OIDCConnectScopes: updatedSettings.OIDCConnectScopes, + OIDCConnectRedirectURL: updatedSettings.OIDCConnectRedirectURL, + OIDCConnectFrontendRedirectURL: updatedSettings.OIDCConnectFrontendRedirectURL, + OIDCConnectTokenAuthMethod: updatedSettings.OIDCConnectTokenAuthMethod, + OIDCConnectUsePKCE: updatedSettings.OIDCConnectUsePKCE, + OIDCConnectValidateIDToken: updatedSettings.OIDCConnectValidateIDToken, + OIDCConnectAllowedSigningAlgs: updatedSettings.OIDCConnectAllowedSigningAlgs, + OIDCConnectClockSkewSeconds: updatedSettings.OIDCConnectClockSkewSeconds, + OIDCConnectRequireEmailVerified: updatedSettings.OIDCConnectRequireEmailVerified, + OIDCConnectUserInfoEmailPath: updatedSettings.OIDCConnectUserInfoEmailPath, + OIDCConnectUserInfoIDPath: updatedSettings.OIDCConnectUserInfoIDPath, + OIDCConnectUserInfoUsernamePath: updatedSettings.OIDCConnectUserInfoUsernamePath, SiteName: updatedSettings.SiteName, SiteLogo: updatedSettings.SiteLogo, SiteSubtitle: updatedSettings.SiteSubtitle, @@ -679,6 +1002,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { HideCcsImportButton: updatedSettings.HideCcsImportButton, PurchaseSubscriptionEnabled: updatedSettings.PurchaseSubscriptionEnabled, PurchaseSubscriptionURL: updatedSettings.PurchaseSubscriptionURL, + TableDefaultPageSize: updatedSettings.TableDefaultPageSize, + TablePageSizeOptions: updatedSettings.TablePageSizeOptions, CustomMenuItems: dto.ParseCustomMenuItems(updatedSettings.CustomMenuItems), CustomEndpoints: dto.ParseCustomEndpoints(updatedSettings.CustomEndpoints), DefaultConcurrency: updatedSettings.DefaultConcurrency, @@ -702,9 +1027,40 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { EnableFingerprintUnification: updatedSettings.EnableFingerprintUnification, EnableMetadataPassthrough: updatedSettings.EnableMetadataPassthrough, EnableCCHSigning: updatedSettings.EnableCCHSigning, + PaymentEnabled: updatedPaymentCfg.Enabled, + PaymentMinAmount: updatedPaymentCfg.MinAmount, + PaymentMaxAmount: updatedPaymentCfg.MaxAmount, + PaymentDailyLimit: updatedPaymentCfg.DailyLimit, + PaymentOrderTimeoutMin: updatedPaymentCfg.OrderTimeoutMin, + PaymentMaxPendingOrders: updatedPaymentCfg.MaxPendingOrders, + PaymentEnabledTypes: updatedPaymentCfg.EnabledTypes, + PaymentBalanceDisabled: updatedPaymentCfg.BalanceDisabled, + PaymentLoadBalanceStrat: updatedPaymentCfg.LoadBalanceStrategy, + PaymentProductNamePrefix: updatedPaymentCfg.ProductNamePrefix, + PaymentProductNameSuffix: updatedPaymentCfg.ProductNameSuffix, + PaymentHelpImageURL: updatedPaymentCfg.HelpImageURL, + PaymentHelpText: updatedPaymentCfg.HelpText, + PaymentCancelRateLimitEnabled: updatedPaymentCfg.CancelRateLimitEnabled, + PaymentCancelRateLimitMax: updatedPaymentCfg.CancelRateLimitMax, + PaymentCancelRateLimitWindow: updatedPaymentCfg.CancelRateLimitWindow, + PaymentCancelRateLimitUnit: updatedPaymentCfg.CancelRateLimitUnit, + PaymentCancelRateLimitMode: updatedPaymentCfg.CancelRateLimitMode, }) } +// hasPaymentFields returns true if any payment-related field was explicitly provided. +func hasPaymentFields(req UpdateSettingsRequest) bool { + return req.PaymentEnabled != nil || req.PaymentMinAmount != nil || + req.PaymentMaxAmount != nil || req.PaymentDailyLimit != nil || + req.PaymentOrderTimeoutMin != nil || req.PaymentMaxPendingOrders != nil || + req.PaymentEnabledTypes != nil || req.PaymentBalanceDisabled != nil || + req.PaymentLoadBalanceStrat != nil || req.PaymentProductNamePrefix != nil || + req.PaymentProductNameSuffix != nil || req.PaymentHelpImageURL != nil || + req.PaymentHelpText != nil || req.PaymentCancelRateLimitEnabled != nil || + req.PaymentCancelRateLimitMax != nil || req.PaymentCancelRateLimitWindow != nil || + req.PaymentCancelRateLimitUnit != nil || req.PaymentCancelRateLimitMode != nil +} + func (h *SettingHandler) auditSettingsUpdate(c *gin.Context, before *service.SystemSettings, after *service.SystemSettings, req UpdateSettingsRequest) { if before == nil || after == nil { return @@ -787,6 +1143,72 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings, if before.LinuxDoConnectRedirectURL != after.LinuxDoConnectRedirectURL { changed = append(changed, "linuxdo_connect_redirect_url") } + if before.OIDCConnectEnabled != after.OIDCConnectEnabled { + changed = append(changed, "oidc_connect_enabled") + } + if before.OIDCConnectProviderName != after.OIDCConnectProviderName { + changed = append(changed, "oidc_connect_provider_name") + } + if before.OIDCConnectClientID != after.OIDCConnectClientID { + changed = append(changed, "oidc_connect_client_id") + } + if req.OIDCConnectClientSecret != "" { + changed = append(changed, "oidc_connect_client_secret") + } + if before.OIDCConnectIssuerURL != after.OIDCConnectIssuerURL { + changed = append(changed, "oidc_connect_issuer_url") + } + if before.OIDCConnectDiscoveryURL != after.OIDCConnectDiscoveryURL { + changed = append(changed, "oidc_connect_discovery_url") + } + if before.OIDCConnectAuthorizeURL != after.OIDCConnectAuthorizeURL { + changed = append(changed, "oidc_connect_authorize_url") + } + if before.OIDCConnectTokenURL != after.OIDCConnectTokenURL { + changed = append(changed, "oidc_connect_token_url") + } + if before.OIDCConnectUserInfoURL != after.OIDCConnectUserInfoURL { + changed = append(changed, "oidc_connect_userinfo_url") + } + if before.OIDCConnectJWKSURL != after.OIDCConnectJWKSURL { + changed = append(changed, "oidc_connect_jwks_url") + } + if before.OIDCConnectScopes != after.OIDCConnectScopes { + changed = append(changed, "oidc_connect_scopes") + } + if before.OIDCConnectRedirectURL != after.OIDCConnectRedirectURL { + changed = append(changed, "oidc_connect_redirect_url") + } + if before.OIDCConnectFrontendRedirectURL != after.OIDCConnectFrontendRedirectURL { + changed = append(changed, "oidc_connect_frontend_redirect_url") + } + if before.OIDCConnectTokenAuthMethod != after.OIDCConnectTokenAuthMethod { + changed = append(changed, "oidc_connect_token_auth_method") + } + if before.OIDCConnectUsePKCE != after.OIDCConnectUsePKCE { + changed = append(changed, "oidc_connect_use_pkce") + } + if before.OIDCConnectValidateIDToken != after.OIDCConnectValidateIDToken { + changed = append(changed, "oidc_connect_validate_id_token") + } + if before.OIDCConnectAllowedSigningAlgs != after.OIDCConnectAllowedSigningAlgs { + changed = append(changed, "oidc_connect_allowed_signing_algs") + } + if before.OIDCConnectClockSkewSeconds != after.OIDCConnectClockSkewSeconds { + changed = append(changed, "oidc_connect_clock_skew_seconds") + } + if before.OIDCConnectRequireEmailVerified != after.OIDCConnectRequireEmailVerified { + changed = append(changed, "oidc_connect_require_email_verified") + } + if before.OIDCConnectUserInfoEmailPath != after.OIDCConnectUserInfoEmailPath { + changed = append(changed, "oidc_connect_userinfo_email_path") + } + if before.OIDCConnectUserInfoIDPath != after.OIDCConnectUserInfoIDPath { + changed = append(changed, "oidc_connect_userinfo_id_path") + } + if before.OIDCConnectUserInfoUsernamePath != after.OIDCConnectUserInfoUsernamePath { + changed = append(changed, "oidc_connect_userinfo_username_path") + } if before.SiteName != after.SiteName { changed = append(changed, "site_name") } @@ -871,6 +1293,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings, if before.PurchaseSubscriptionURL != after.PurchaseSubscriptionURL { changed = append(changed, "purchase_subscription_url") } + if before.TableDefaultPageSize != after.TableDefaultPageSize { + changed = append(changed, "table_default_page_size") + } + if !equalIntSlice(before.TablePageSizeOptions, after.TablePageSizeOptions) { + changed = append(changed, "table_page_size_options") + } if before.CustomMenuItems != after.CustomMenuItems { changed = append(changed, "custom_menu_items") } @@ -927,6 +1355,18 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool { return true } +func equalIntSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + // TestSMTPRequest 测试SMTP连接请求 type TestSMTPRequest struct { SMTPHost string `json:"smtp_host"` diff --git a/backend/internal/handler/admin/usage_handler.go b/backend/internal/handler/admin/usage_handler.go index 2967b3840e8c5c29d714780d4762ce356cf051a5..0857a13880b351ab9409b30bd50eeabc5376b884 100644 --- a/backend/internal/handler/admin/usage_handler.go +++ b/backend/internal/handler/admin/usage_handler.go @@ -165,7 +165,12 @@ func (h *UsageHandler) List(c *gin.Context) { endTime = &t } - params := pagination.PaginationParams{Page: page, PageSize: pageSize} + params := pagination.PaginationParams{ + Page: page, + PageSize: pageSize, + SortBy: c.DefaultQuery("sort_by", "created_at"), + SortOrder: c.DefaultQuery("sort_order", "desc"), + } filters := usagestats.UsageLogFilters{ UserID: userID, APIKeyID: apiKeyID, @@ -339,7 +344,7 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) { } // Limit to 30 results - users, _, err := h.adminService.ListUsers(c.Request.Context(), 1, 30, service.UserListFilters{Search: keyword}) + users, _, err := h.adminService.ListUsers(c.Request.Context(), 1, 30, service.UserListFilters{Search: keyword}, "email", "asc") if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/handler/admin/usage_handler_request_type_test.go b/backend/internal/handler/admin/usage_handler_request_type_test.go index 3f158316fecd03b2c93b098750d67a4ad997b44f..882cbe9362b2259006f405a84a9ae504f8272716 100644 --- a/backend/internal/handler/admin/usage_handler_request_type_test.go +++ b/backend/internal/handler/admin/usage_handler_request_type_test.go @@ -15,11 +15,13 @@ import ( type adminUsageRepoCapture struct { service.UsageLogRepository + listParams pagination.PaginationParams listFilters usagestats.UsageLogFilters statsFilters usagestats.UsageLogFilters } func (s *adminUsageRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]service.UsageLog, *pagination.PaginationResult, error) { + s.listParams = params s.listFilters = filters return []service.UsageLog{}, &pagination.PaginationResult{ Total: 0, diff --git a/backend/internal/handler/admin/usage_handler_sort_test.go b/backend/internal/handler/admin/usage_handler_sort_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dac826762a046df77d5ae20e6a386bde0804545c --- /dev/null +++ b/backend/internal/handler/admin/usage_handler_sort_test.go @@ -0,0 +1,35 @@ +package admin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAdminUsageListSortParams(t *testing.T) { + repo := &adminUsageRepoCapture{} + router := newAdminUsageRequestTypeTestRouter(repo) + + req := httptest.NewRequest(http.MethodGet, "/admin/usage?sort_by=model&sort_order=ASC", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "model", repo.listParams.SortBy) + require.Equal(t, "ASC", repo.listParams.SortOrder) +} + +func TestAdminUsageListSortDefaults(t *testing.T) { + repo := &adminUsageRepoCapture{} + router := newAdminUsageRequestTypeTestRouter(repo) + + req := httptest.NewRequest(http.MethodGet, "/admin/usage", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "created_at", repo.listParams.SortBy) + require.Equal(t, "desc", repo.listParams.SortOrder) +} diff --git a/backend/internal/handler/admin/user_handler.go b/backend/internal/handler/admin/user_handler.go index a357657e20534f78c254c5d6c2860e0bfebadd67..1453bd0739ddf6a518e010db4f3256fcdc8692fe 100644 --- a/backend/internal/handler/admin/user_handler.go +++ b/backend/internal/handler/admin/user_handler.go @@ -91,12 +91,14 @@ func (h *UserHandler) List(c *gin.Context) { GroupName: strings.TrimSpace(c.Query("group_name")), Attributes: parseAttributeFilters(c), } + sortBy := c.DefaultQuery("sort_by", "created_at") + sortOrder := c.DefaultQuery("sort_order", "desc") if raw, ok := c.GetQuery("include_subscriptions"); ok { includeSubscriptions := parseBoolQueryWithDefault(raw, true) filters.IncludeSubscriptions = &includeSubscriptions } - users, total, err := h.adminService.ListUsers(c.Request.Context(), page, pageSize, filters) + users, total, err := h.adminService.ListUsers(c.Request.Context(), page, pageSize, filters, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return @@ -290,8 +292,10 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) { } page, pageSize := response.ParsePagination(c) + sortBy := c.DefaultQuery("sort_by", "created_at") + sortOrder := c.DefaultQuery("sort_order", "desc") - keys, total, err := h.adminService.GetUserAPIKeys(c.Request.Context(), userID, page, pageSize) + keys, total, err := h.adminService.GetUserAPIKeys(c.Request.Context(), userID, page, pageSize, sortBy, sortOrder) if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/handler/api_key_handler.go b/backend/internal/handler/api_key_handler.go index 951aed08db73b0aa90abd6ecc239e4d728f565dc..9d6c6c15230d4c18ba468c0e3f38c944010eda6b 100644 --- a/backend/internal/handler/api_key_handler.go +++ b/backend/internal/handler/api_key_handler.go @@ -72,7 +72,12 @@ func (h *APIKeyHandler) List(c *gin.Context) { } page, pageSize := response.ParsePagination(c) - params := pagination.PaginationParams{Page: page, PageSize: pageSize} + params := pagination.PaginationParams{ + Page: page, + PageSize: pageSize, + SortBy: c.DefaultQuery("sort_by", "created_at"), + SortOrder: c.DefaultQuery("sort_order", "desc"), + } // Parse filter parameters var filters service.APIKeyListFilters diff --git a/backend/internal/handler/auth_oidc_oauth.go b/backend/internal/handler/auth_oidc_oauth.go new file mode 100644 index 0000000000000000000000000000000000000000..9d24df88ab1a08a2a436e120e7e367c65b66c881 --- /dev/null +++ b/backend/internal/handler/auth_oidc_oauth.go @@ -0,0 +1,873 @@ +package handler + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "log" + "math/big" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/Wei-Shaw/sub2api/internal/config" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" + "github.com/Wei-Shaw/sub2api/internal/pkg/oauth" + "github.com/Wei-Shaw/sub2api/internal/pkg/response" + "github.com/Wei-Shaw/sub2api/internal/service" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "github.com/imroc/req/v3" + "github.com/tidwall/gjson" +) + +const ( + oidcOAuthCookiePath = "/api/v1/auth/oauth/oidc" + oidcOAuthStateCookieName = "oidc_oauth_state" + oidcOAuthVerifierCookie = "oidc_oauth_verifier" + oidcOAuthRedirectCookie = "oidc_oauth_redirect" + oidcOAuthNonceCookie = "oidc_oauth_nonce" + oidcOAuthCookieMaxAgeSec = 10 * 60 // 10 minutes + oidcOAuthDefaultRedirectTo = "/dashboard" + oidcOAuthDefaultFrontendCB = "/auth/oidc/callback" +) + +type oidcTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` + RefreshToken string `json:"refresh_token,omitempty"` + Scope string `json:"scope,omitempty"` + IDToken string `json:"id_token,omitempty"` +} + +type oidcTokenExchangeError struct { + StatusCode int + ProviderError string + ProviderDescription string + Body string +} + +func (e *oidcTokenExchangeError) Error() string { + if e == nil { + return "" + } + parts := []string{fmt.Sprintf("token exchange status=%d", e.StatusCode)} + if strings.TrimSpace(e.ProviderError) != "" { + parts = append(parts, "error="+strings.TrimSpace(e.ProviderError)) + } + if strings.TrimSpace(e.ProviderDescription) != "" { + parts = append(parts, "error_description="+strings.TrimSpace(e.ProviderDescription)) + } + return strings.Join(parts, " ") +} + +type oidcIDTokenClaims struct { + Email string `json:"email,omitempty"` + EmailVerified *bool `json:"email_verified,omitempty"` + PreferredUsername string `json:"preferred_username,omitempty"` + Name string `json:"name,omitempty"` + Nonce string `json:"nonce,omitempty"` + Azp string `json:"azp,omitempty"` + jwt.RegisteredClaims +} + +type oidcUserInfoClaims struct { + Email string + Username string + Subject string + EmailVerified *bool +} + +type oidcJWKSet struct { + Keys []oidcJWK `json:"keys"` +} + +type oidcJWK struct { + Kty string `json:"kty"` + Kid string `json:"kid"` + Use string `json:"use"` + Alg string `json:"alg"` + + N string `json:"n"` + E string `json:"e"` + + Crv string `json:"crv"` + X string `json:"x"` + Y string `json:"y"` +} + +// OIDCOAuthStart 启动通用 OIDC OAuth 登录流程。 +// GET /api/v1/auth/oauth/oidc/start?redirect=/dashboard +func (h *AuthHandler) OIDCOAuthStart(c *gin.Context) { + cfg, err := h.getOIDCOAuthConfig(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + + state, err := oauth.GenerateState() + if err != nil { + response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_STATE_GEN_FAILED", "failed to generate oauth state").WithCause(err)) + return + } + + redirectTo := sanitizeFrontendRedirectPath(c.Query("redirect")) + if redirectTo == "" { + redirectTo = oidcOAuthDefaultRedirectTo + } + + secureCookie := isRequestHTTPS(c) + oidcSetCookie(c, oidcOAuthStateCookieName, encodeCookieValue(state), oidcOAuthCookieMaxAgeSec, secureCookie) + oidcSetCookie(c, oidcOAuthRedirectCookie, encodeCookieValue(redirectTo), oidcOAuthCookieMaxAgeSec, secureCookie) + + codeChallenge := "" + if cfg.UsePKCE { + verifier, genErr := oauth.GenerateCodeVerifier() + if genErr != nil { + response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_PKCE_GEN_FAILED", "failed to generate pkce verifier").WithCause(genErr)) + return + } + codeChallenge = oauth.GenerateCodeChallenge(verifier) + oidcSetCookie(c, oidcOAuthVerifierCookie, encodeCookieValue(verifier), oidcOAuthCookieMaxAgeSec, secureCookie) + } + + nonce := "" + if cfg.ValidateIDToken { + nonce, err = oauth.GenerateState() + if err != nil { + response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_NONCE_GEN_FAILED", "failed to generate oauth nonce").WithCause(err)) + return + } + oidcSetCookie(c, oidcOAuthNonceCookie, encodeCookieValue(nonce), oidcOAuthCookieMaxAgeSec, secureCookie) + } + + redirectURI := strings.TrimSpace(cfg.RedirectURL) + if redirectURI == "" { + response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth redirect url not configured")) + return + } + + authURL, err := buildOIDCAuthorizeURL(cfg, state, nonce, codeChallenge, redirectURI) + if err != nil { + response.ErrorFrom(c, infraerrors.InternalServer("OAUTH_BUILD_URL_FAILED", "failed to build oauth authorization url").WithCause(err)) + return + } + + c.Redirect(http.StatusFound, authURL) +} + +// OIDCOAuthCallback 处理 OIDC 回调:校验 id_token、创建/登录用户并重定向到前端。 +// GET /api/v1/auth/oauth/oidc/callback?code=...&state=... +func (h *AuthHandler) OIDCOAuthCallback(c *gin.Context) { + cfg, cfgErr := h.getOIDCOAuthConfig(c.Request.Context()) + if cfgErr != nil { + response.ErrorFrom(c, cfgErr) + return + } + + frontendCallback := strings.TrimSpace(cfg.FrontendRedirectURL) + if frontendCallback == "" { + frontendCallback = oidcOAuthDefaultFrontendCB + } + + if providerErr := strings.TrimSpace(c.Query("error")); providerErr != "" { + redirectOAuthError(c, frontendCallback, "provider_error", providerErr, c.Query("error_description")) + return + } + + code := strings.TrimSpace(c.Query("code")) + state := strings.TrimSpace(c.Query("state")) + if code == "" || state == "" { + redirectOAuthError(c, frontendCallback, "missing_params", "missing code/state", "") + return + } + + secureCookie := isRequestHTTPS(c) + defer func() { + oidcClearCookie(c, oidcOAuthStateCookieName, secureCookie) + oidcClearCookie(c, oidcOAuthVerifierCookie, secureCookie) + oidcClearCookie(c, oidcOAuthRedirectCookie, secureCookie) + oidcClearCookie(c, oidcOAuthNonceCookie, secureCookie) + }() + + expectedState, err := readCookieDecoded(c, oidcOAuthStateCookieName) + if err != nil || expectedState == "" || state != expectedState { + redirectOAuthError(c, frontendCallback, "invalid_state", "invalid oauth state", "") + return + } + + redirectTo, _ := readCookieDecoded(c, oidcOAuthRedirectCookie) + redirectTo = sanitizeFrontendRedirectPath(redirectTo) + if redirectTo == "" { + redirectTo = oidcOAuthDefaultRedirectTo + } + + codeVerifier := "" + if cfg.UsePKCE { + codeVerifier, _ = readCookieDecoded(c, oidcOAuthVerifierCookie) + if codeVerifier == "" { + redirectOAuthError(c, frontendCallback, "missing_verifier", "missing pkce verifier", "") + return + } + } + + expectedNonce := "" + if cfg.ValidateIDToken { + expectedNonce, _ = readCookieDecoded(c, oidcOAuthNonceCookie) + if expectedNonce == "" { + redirectOAuthError(c, frontendCallback, "missing_nonce", "missing oauth nonce", "") + return + } + } + + redirectURI := strings.TrimSpace(cfg.RedirectURL) + if redirectURI == "" { + redirectOAuthError(c, frontendCallback, "config_error", "oauth redirect url not configured", "") + return + } + + tokenResp, err := oidcExchangeCode(c.Request.Context(), cfg, code, redirectURI, codeVerifier) + if err != nil { + description := "" + var exchangeErr *oidcTokenExchangeError + if errors.As(err, &exchangeErr) && exchangeErr != nil { + log.Printf( + "[OIDC OAuth] token exchange failed: status=%d provider_error=%q provider_description=%q body=%s", + exchangeErr.StatusCode, + exchangeErr.ProviderError, + exchangeErr.ProviderDescription, + truncateLogValue(exchangeErr.Body, 2048), + ) + description = exchangeErr.Error() + } else { + log.Printf("[OIDC OAuth] token exchange failed: %v", err) + description = err.Error() + } + redirectOAuthError(c, frontendCallback, "token_exchange_failed", "failed to exchange oauth code", singleLine(description)) + return + } + + if cfg.ValidateIDToken && strings.TrimSpace(tokenResp.IDToken) == "" { + redirectOAuthError(c, frontendCallback, "missing_id_token", "missing id_token", "") + return + } + + idClaims, err := oidcParseAndValidateIDToken(c.Request.Context(), cfg, tokenResp.IDToken, expectedNonce) + if err != nil { + log.Printf("[OIDC OAuth] id_token validation failed: %v", err) + redirectOAuthError(c, frontendCallback, "invalid_id_token", "failed to validate id_token", "") + return + } + + userInfoClaims, err := oidcFetchUserInfo(c.Request.Context(), cfg, tokenResp) + if err != nil { + log.Printf("[OIDC OAuth] userinfo fetch failed: %v", err) + redirectOAuthError(c, frontendCallback, "userinfo_failed", "failed to fetch user info", "") + return + } + + subject := strings.TrimSpace(idClaims.Subject) + if subject == "" { + subject = strings.TrimSpace(userInfoClaims.Subject) + } + if subject == "" { + redirectOAuthError(c, frontendCallback, "missing_subject", "missing subject claim", "") + return + } + issuer := strings.TrimSpace(idClaims.Issuer) + if issuer == "" { + issuer = strings.TrimSpace(cfg.IssuerURL) + } + if issuer == "" { + redirectOAuthError(c, frontendCallback, "missing_issuer", "missing issuer claim", "") + return + } + + emailVerified := userInfoClaims.EmailVerified + if emailVerified == nil { + emailVerified = idClaims.EmailVerified + } + if cfg.RequireEmailVerified { + if emailVerified == nil || !*emailVerified { + redirectOAuthError(c, frontendCallback, "email_not_verified", "email is not verified", "") + return + } + } + + identityKey := oidcIdentityKey(issuer, subject) + email := oidcSelectLoginEmail(userInfoClaims.Email, idClaims.Email, identityKey) + username := firstNonEmpty( + userInfoClaims.Username, + idClaims.PreferredUsername, + idClaims.Name, + oidcFallbackUsername(subject), + ) + + // 传入空邀请码;如果需要邀请码,服务层返回 ErrOAuthInvitationRequired + tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username, "") + if err != nil { + if errors.Is(err, service.ErrOAuthInvitationRequired) { + pendingToken, tokenErr := h.authService.CreatePendingOAuthToken(email, username) + if tokenErr != nil { + redirectOAuthError(c, frontendCallback, "login_failed", "service_error", "") + return + } + fragment := url.Values{} + fragment.Set("error", "invitation_required") + fragment.Set("pending_oauth_token", pendingToken) + fragment.Set("redirect", redirectTo) + redirectWithFragment(c, frontendCallback, fragment) + return + } + redirectOAuthError(c, frontendCallback, "login_failed", infraerrors.Reason(err), infraerrors.Message(err)) + return + } + + fragment := url.Values{} + fragment.Set("access_token", tokenPair.AccessToken) + fragment.Set("refresh_token", tokenPair.RefreshToken) + fragment.Set("expires_in", fmt.Sprintf("%d", tokenPair.ExpiresIn)) + fragment.Set("token_type", "Bearer") + fragment.Set("redirect", redirectTo) + redirectWithFragment(c, frontendCallback, fragment) +} + +type completeOIDCOAuthRequest struct { + PendingOAuthToken string `json:"pending_oauth_token" binding:"required"` + InvitationCode string `json:"invitation_code" binding:"required"` +} + +// CompleteOIDCOAuthRegistration completes a pending OAuth registration by validating +// the invitation code and creating the user account. +// POST /api/v1/auth/oauth/oidc/complete-registration +func (h *AuthHandler) CompleteOIDCOAuthRegistration(c *gin.Context) { + var req completeOIDCOAuthRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "INVALID_REQUEST", "message": err.Error()}) + return + } + + email, username, err := h.authService.VerifyPendingOAuthToken(req.PendingOAuthToken) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "INVALID_TOKEN", "message": "invalid or expired registration token"}) + return + } + + tokenPair, _, err := h.authService.LoginOrRegisterOAuthWithTokenPair(c.Request.Context(), email, username, req.InvitationCode) + if err != nil { + response.ErrorFrom(c, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "access_token": tokenPair.AccessToken, + "refresh_token": tokenPair.RefreshToken, + "expires_in": tokenPair.ExpiresIn, + "token_type": "Bearer", + }) +} + +func (h *AuthHandler) getOIDCOAuthConfig(ctx context.Context) (config.OIDCConnectConfig, error) { + if h != nil && h.settingSvc != nil { + return h.settingSvc.GetOIDCConnectOAuthConfig(ctx) + } + if h == nil || h.cfg == nil { + return config.OIDCConnectConfig{}, infraerrors.ServiceUnavailable("CONFIG_NOT_READY", "config not loaded") + } + if !h.cfg.OIDC.Enabled { + return config.OIDCConnectConfig{}, infraerrors.NotFound("OAUTH_DISABLED", "oauth login is disabled") + } + return h.cfg.OIDC, nil +} + +func oidcExchangeCode( + ctx context.Context, + cfg config.OIDCConnectConfig, + code string, + redirectURI string, + codeVerifier string, +) (*oidcTokenResponse, error) { + client := req.C().SetTimeout(30 * time.Second) + + form := url.Values{} + form.Set("grant_type", "authorization_code") + form.Set("client_id", cfg.ClientID) + form.Set("code", code) + form.Set("redirect_uri", redirectURI) + if cfg.UsePKCE { + form.Set("code_verifier", codeVerifier) + } + + r := client.R(). + SetContext(ctx). + SetHeader("Accept", "application/json") + + switch strings.ToLower(strings.TrimSpace(cfg.TokenAuthMethod)) { + case "", "client_secret_post": + form.Set("client_secret", cfg.ClientSecret) + case "client_secret_basic": + r.SetBasicAuth(cfg.ClientID, cfg.ClientSecret) + case "none": + default: + return nil, fmt.Errorf("unsupported token_auth_method: %s", cfg.TokenAuthMethod) + } + + resp, err := r.SetFormDataFromValues(form).Post(cfg.TokenURL) + if err != nil { + return nil, fmt.Errorf("request token: %w", err) + } + body := strings.TrimSpace(resp.String()) + if !resp.IsSuccessState() { + providerErr, providerDesc := parseOAuthProviderError(body) + return nil, &oidcTokenExchangeError{ + StatusCode: resp.StatusCode, + ProviderError: providerErr, + ProviderDescription: providerDesc, + Body: body, + } + } + + tokenResp, ok := oidcParseTokenResponse(body) + if !ok { + return nil, &oidcTokenExchangeError{StatusCode: resp.StatusCode, Body: body} + } + if strings.TrimSpace(tokenResp.TokenType) == "" { + tokenResp.TokenType = "Bearer" + } + if strings.TrimSpace(tokenResp.AccessToken) == "" && strings.TrimSpace(tokenResp.IDToken) == "" { + return nil, &oidcTokenExchangeError{StatusCode: resp.StatusCode, Body: body} + } + return tokenResp, nil +} + +func oidcParseTokenResponse(body string) (*oidcTokenResponse, bool) { + body = strings.TrimSpace(body) + if body == "" { + return nil, false + } + + accessToken := strings.TrimSpace(getGJSON(body, "access_token")) + idToken := strings.TrimSpace(getGJSON(body, "id_token")) + if accessToken != "" || idToken != "" { + tokenType := strings.TrimSpace(getGJSON(body, "token_type")) + refreshToken := strings.TrimSpace(getGJSON(body, "refresh_token")) + scope := strings.TrimSpace(getGJSON(body, "scope")) + expiresIn := gjson.Get(body, "expires_in").Int() + return &oidcTokenResponse{ + AccessToken: accessToken, + TokenType: tokenType, + ExpiresIn: expiresIn, + RefreshToken: refreshToken, + Scope: scope, + IDToken: idToken, + }, true + } + + values, err := url.ParseQuery(body) + if err != nil { + return nil, false + } + accessToken = strings.TrimSpace(values.Get("access_token")) + idToken = strings.TrimSpace(values.Get("id_token")) + if accessToken == "" && idToken == "" { + return nil, false + } + expiresIn := int64(0) + if raw := strings.TrimSpace(values.Get("expires_in")); raw != "" { + if v, parseErr := strconv.ParseInt(raw, 10, 64); parseErr == nil { + expiresIn = v + } + } + return &oidcTokenResponse{ + AccessToken: accessToken, + TokenType: strings.TrimSpace(values.Get("token_type")), + ExpiresIn: expiresIn, + RefreshToken: strings.TrimSpace(values.Get("refresh_token")), + Scope: strings.TrimSpace(values.Get("scope")), + IDToken: idToken, + }, true +} + +func oidcFetchUserInfo( + ctx context.Context, + cfg config.OIDCConnectConfig, + token *oidcTokenResponse, +) (*oidcUserInfoClaims, error) { + if strings.TrimSpace(cfg.UserInfoURL) == "" { + return &oidcUserInfoClaims{}, nil + } + if token == nil || strings.TrimSpace(token.AccessToken) == "" { + return nil, errors.New("missing access_token for userinfo request") + } + + client := req.C().SetTimeout(30 * time.Second) + authorization, err := buildBearerAuthorization(token.TokenType, token.AccessToken) + if err != nil { + return nil, fmt.Errorf("invalid token for userinfo request: %w", err) + } + + resp, err := client.R(). + SetContext(ctx). + SetHeader("Accept", "application/json"). + SetHeader("Authorization", authorization). + Get(cfg.UserInfoURL) + if err != nil { + return nil, fmt.Errorf("request userinfo: %w", err) + } + if !resp.IsSuccessState() { + return nil, fmt.Errorf("userinfo status=%d", resp.StatusCode) + } + + return oidcParseUserInfo(resp.String(), cfg), nil +} + +func oidcParseUserInfo(body string, cfg config.OIDCConnectConfig) *oidcUserInfoClaims { + claims := &oidcUserInfoClaims{} + claims.Email = firstNonEmpty( + getGJSON(body, cfg.UserInfoEmailPath), + getGJSON(body, "email"), + getGJSON(body, "user.email"), + getGJSON(body, "data.email"), + getGJSON(body, "attributes.email"), + ) + claims.Username = firstNonEmpty( + getGJSON(body, cfg.UserInfoUsernamePath), + getGJSON(body, "preferred_username"), + getGJSON(body, "username"), + getGJSON(body, "name"), + getGJSON(body, "user.username"), + getGJSON(body, "user.name"), + ) + claims.Subject = firstNonEmpty( + getGJSON(body, cfg.UserInfoIDPath), + getGJSON(body, "sub"), + getGJSON(body, "id"), + getGJSON(body, "user_id"), + getGJSON(body, "uid"), + getGJSON(body, "user.id"), + ) + if verified, ok := getGJSONBool(body, "email_verified"); ok { + claims.EmailVerified = &verified + } + claims.Email = strings.TrimSpace(claims.Email) + claims.Username = strings.TrimSpace(claims.Username) + claims.Subject = strings.TrimSpace(claims.Subject) + return claims +} + +func getGJSONBool(body string, path string) (bool, bool) { + path = strings.TrimSpace(path) + if path == "" { + return false, false + } + res := gjson.Get(body, path) + if !res.Exists() { + return false, false + } + return res.Bool(), true +} + +func buildOIDCAuthorizeURL(cfg config.OIDCConnectConfig, state, nonce, codeChallenge, redirectURI string) (string, error) { + u, err := url.Parse(cfg.AuthorizeURL) + if err != nil { + return "", fmt.Errorf("parse authorize_url: %w", err) + } + + q := u.Query() + q.Set("response_type", "code") + q.Set("client_id", cfg.ClientID) + q.Set("redirect_uri", redirectURI) + if strings.TrimSpace(cfg.Scopes) != "" { + q.Set("scope", cfg.Scopes) + } + q.Set("state", state) + if strings.TrimSpace(nonce) != "" { + q.Set("nonce", nonce) + } + if cfg.UsePKCE { + q.Set("code_challenge", codeChallenge) + q.Set("code_challenge_method", "S256") + } + + u.RawQuery = q.Encode() + return u.String(), nil +} + +func oidcParseAndValidateIDToken(ctx context.Context, cfg config.OIDCConnectConfig, idToken string, expectedNonce string) (*oidcIDTokenClaims, error) { + idToken = strings.TrimSpace(idToken) + if idToken == "" { + return nil, errors.New("missing id_token") + } + allowed := oidcAllowedSigningAlgs(cfg.AllowedSigningAlgs) + if len(allowed) == 0 { + return nil, errors.New("empty allowed signing algorithms") + } + + jwks, err := oidcFetchJWKSet(ctx, cfg.JWKSURL) + if err != nil { + return nil, err + } + leeway := time.Duration(cfg.ClockSkewSeconds) * time.Second + claims := &oidcIDTokenClaims{} + + parsed, err := jwt.ParseWithClaims( + idToken, + claims, + func(token *jwt.Token) (any, error) { + alg := strings.TrimSpace(token.Method.Alg()) + if !containsString(allowed, alg) { + return nil, fmt.Errorf("unexpected signing algorithm: %s", alg) + } + kid, _ := token.Header["kid"].(string) + return oidcFindPublicKey(jwks, strings.TrimSpace(kid), alg) + }, + jwt.WithValidMethods(allowed), + jwt.WithAudience(cfg.ClientID), + jwt.WithIssuer(cfg.IssuerURL), + jwt.WithLeeway(leeway), + ) + if err != nil { + return nil, err + } + if !parsed.Valid { + return nil, errors.New("id_token invalid") + } + if strings.TrimSpace(claims.Subject) == "" { + return nil, errors.New("id_token missing sub") + } + if expectedNonce != "" && strings.TrimSpace(claims.Nonce) != strings.TrimSpace(expectedNonce) { + return nil, errors.New("id_token nonce mismatch") + } + if len(claims.Audience) > 1 { + if strings.TrimSpace(claims.Azp) == "" || strings.TrimSpace(claims.Azp) != strings.TrimSpace(cfg.ClientID) { + return nil, errors.New("id_token azp mismatch") + } + } + return claims, nil +} + +func oidcAllowedSigningAlgs(raw string) []string { + if strings.TrimSpace(raw) == "" { + return []string{"RS256", "ES256", "PS256"} + } + seen := make(map[string]struct{}) + out := make([]string, 0, 4) + for _, part := range strings.Split(raw, ",") { + alg := strings.ToUpper(strings.TrimSpace(part)) + if alg == "" { + continue + } + if _, ok := seen[alg]; ok { + continue + } + seen[alg] = struct{}{} + out = append(out, alg) + } + return out +} + +func oidcFetchJWKSet(ctx context.Context, jwksURL string) (*oidcJWKSet, error) { + jwksURL = strings.TrimSpace(jwksURL) + if jwksURL == "" { + return nil, errors.New("missing jwks_url") + } + resp, err := req.C(). + SetTimeout(30*time.Second). + R(). + SetContext(ctx). + SetHeader("Accept", "application/json"). + Get(jwksURL) + if err != nil { + return nil, fmt.Errorf("request jwks: %w", err) + } + if !resp.IsSuccessState() { + return nil, fmt.Errorf("jwks status=%d", resp.StatusCode) + } + set := &oidcJWKSet{} + if err := json.Unmarshal(resp.Bytes(), set); err != nil { + return nil, fmt.Errorf("parse jwks: %w", err) + } + if len(set.Keys) == 0 { + return nil, errors.New("jwks empty keys") + } + return set, nil +} + +func oidcFindPublicKey(set *oidcJWKSet, kid, alg string) (any, error) { + if set == nil { + return nil, errors.New("jwks not loaded") + } + alg = strings.ToUpper(strings.TrimSpace(alg)) + kid = strings.TrimSpace(kid) + + var lastErr error + for i := range set.Keys { + k := set.Keys[i] + if strings.TrimSpace(k.Use) != "" && !strings.EqualFold(strings.TrimSpace(k.Use), "sig") { + continue + } + if kid != "" && strings.TrimSpace(k.Kid) != kid { + continue + } + if strings.TrimSpace(k.Alg) != "" && !strings.EqualFold(strings.TrimSpace(k.Alg), alg) { + continue + } + pk, err := k.publicKey() + if err != nil { + lastErr = err + continue + } + if pk != nil { + return pk, nil + } + } + if lastErr != nil { + return nil, lastErr + } + if kid != "" { + return nil, fmt.Errorf("jwk not found for kid=%s", kid) + } + return nil, errors.New("jwk not found") +} + +func (k oidcJWK) publicKey() (any, error) { + switch strings.ToUpper(strings.TrimSpace(k.Kty)) { + case "RSA": + n, err := decodeBase64URLBigInt(k.N) + if err != nil { + return nil, fmt.Errorf("decode rsa n: %w", err) + } + eBytes, err := base64.RawURLEncoding.DecodeString(strings.TrimSpace(k.E)) + if err != nil { + return nil, fmt.Errorf("decode rsa e: %w", err) + } + if len(eBytes) == 0 { + return nil, errors.New("empty rsa e") + } + e := 0 + for _, b := range eBytes { + e = (e << 8) | int(b) + } + if e <= 0 { + return nil, errors.New("invalid rsa exponent") + } + if n.Sign() <= 0 { + return nil, errors.New("invalid rsa modulus") + } + return &rsa.PublicKey{N: n, E: e}, nil + case "EC": + var curve elliptic.Curve + switch strings.TrimSpace(k.Crv) { + case "P-256": + curve = elliptic.P256() + case "P-384": + curve = elliptic.P384() + case "P-521": + curve = elliptic.P521() + default: + return nil, fmt.Errorf("unsupported ec curve: %s", k.Crv) + } + x, err := decodeBase64URLBigInt(k.X) + if err != nil { + return nil, fmt.Errorf("decode ec x: %w", err) + } + y, err := decodeBase64URLBigInt(k.Y) + if err != nil { + return nil, fmt.Errorf("decode ec y: %w", err) + } + if !curve.IsOnCurve(x, y) { + return nil, errors.New("ec point is not on curve") + } + return &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, nil + default: + return nil, fmt.Errorf("unsupported jwk kty: %s", k.Kty) + } +} + +func decodeBase64URLBigInt(raw string) (*big.Int, error) { + buf, err := base64.RawURLEncoding.DecodeString(strings.TrimSpace(raw)) + if err != nil { + return nil, err + } + if len(buf) == 0 { + return nil, errors.New("empty value") + } + return new(big.Int).SetBytes(buf), nil +} + +func containsString(values []string, target string) bool { + target = strings.TrimSpace(target) + for _, v := range values { + if strings.EqualFold(strings.TrimSpace(v), target) { + return true + } + } + return false +} + +func oidcIdentityKey(issuer, subject string) string { + issuer = strings.TrimSpace(strings.ToLower(issuer)) + subject = strings.TrimSpace(subject) + return issuer + "\x1f" + subject +} + +func oidcSyntheticEmailFromIdentityKey(identityKey string) string { + identityKey = strings.TrimSpace(identityKey) + if identityKey == "" { + return "" + } + sum := sha256.Sum256([]byte(identityKey)) + return "oidc-" + hex.EncodeToString(sum[:16]) + service.OIDCConnectSyntheticEmailDomain +} + +func oidcSelectLoginEmail(userInfoEmail, idTokenEmail, identityKey string) string { + email := strings.TrimSpace(firstNonEmpty(userInfoEmail, idTokenEmail)) + if email != "" { + return email + } + return oidcSyntheticEmailFromIdentityKey(identityKey) +} + +func oidcFallbackUsername(subject string) string { + subject = strings.TrimSpace(subject) + if subject == "" { + return "oidc_user" + } + sum := sha256.Sum256([]byte(subject)) + return "oidc_" + hex.EncodeToString(sum[:])[:12] +} + +func oidcSetCookie(c *gin.Context, name, value string, maxAgeSec int, secure bool) { + http.SetCookie(c.Writer, &http.Cookie{ + Name: name, + Value: value, + Path: oidcOAuthCookiePath, + MaxAge: maxAgeSec, + HttpOnly: true, + Secure: secure, + SameSite: http.SameSiteLaxMode, + }) +} + +func oidcClearCookie(c *gin.Context, name string, secure bool) { + http.SetCookie(c.Writer, &http.Cookie{ + Name: name, + Value: "", + Path: oidcOAuthCookiePath, + MaxAge: -1, + HttpOnly: true, + Secure: secure, + SameSite: http.SameSiteLaxMode, + }) +} diff --git a/backend/internal/handler/auth_oidc_oauth_test.go b/backend/internal/handler/auth_oidc_oauth_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a161aa77cf6faefaebae46f6a570227263484c90 --- /dev/null +++ b/backend/internal/handler/auth_oidc_oauth_test.go @@ -0,0 +1,120 @@ +package handler + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "encoding/json" + "math/big" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/golang-jwt/jwt/v5" + "github.com/stretchr/testify/require" +) + +func TestOIDCSyntheticEmailStableAndDistinct(t *testing.T) { + k1 := oidcIdentityKey("https://issuer.example.com", "subject-a") + k2 := oidcIdentityKey("https://issuer.example.com", "subject-b") + + e1 := oidcSyntheticEmailFromIdentityKey(k1) + e1Again := oidcSyntheticEmailFromIdentityKey(k1) + e2 := oidcSyntheticEmailFromIdentityKey(k2) + + require.Equal(t, e1, e1Again) + require.NotEqual(t, e1, e2) + require.Contains(t, e1, "@oidc-connect.invalid") +} + +func TestOIDCSelectLoginEmailPrefersRealEmail(t *testing.T) { + identityKey := oidcIdentityKey("https://issuer.example.com", "subject-a") + + email := oidcSelectLoginEmail("user@example.com", "idtoken@example.com", identityKey) + require.Equal(t, "user@example.com", email) + + email = oidcSelectLoginEmail("", "idtoken@example.com", identityKey) + require.Equal(t, "idtoken@example.com", email) + + email = oidcSelectLoginEmail("", "", identityKey) + require.Contains(t, email, "@oidc-connect.invalid") + require.Equal(t, oidcSyntheticEmailFromIdentityKey(identityKey), email) +} + +func TestBuildOIDCAuthorizeURLIncludesNonceAndPKCE(t *testing.T) { + cfg := config.OIDCConnectConfig{ + AuthorizeURL: "https://issuer.example.com/auth", + ClientID: "cid", + Scopes: "openid email profile", + UsePKCE: true, + } + + u, err := buildOIDCAuthorizeURL(cfg, "state123", "nonce123", "challenge123", "https://app.example.com/callback") + require.NoError(t, err) + require.Contains(t, u, "nonce=nonce123") + require.Contains(t, u, "code_challenge=challenge123") + require.Contains(t, u, "code_challenge_method=S256") + require.Contains(t, u, "scope=openid+email+profile") +} + +func TestOIDCParseAndValidateIDToken(t *testing.T) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + kid := "kid-1" + jwks := oidcJWKSet{Keys: []oidcJWK{buildRSAJWK(kid, &priv.PublicKey)}} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.NoError(t, json.NewEncoder(w).Encode(jwks)) + })) + defer srv.Close() + + now := time.Now() + claims := oidcIDTokenClaims{ + Nonce: "nonce-ok", + Azp: "client-1", + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "https://issuer.example.com", + Subject: "subject-1", + Audience: jwt.ClaimStrings{"client-1", "another-aud"}, + IssuedAt: jwt.NewNumericDate(now), + NotBefore: jwt.NewNumericDate(now.Add(-30 * time.Second)), + ExpiresAt: jwt.NewNumericDate(now.Add(5 * time.Minute)), + }, + } + tok := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + tok.Header["kid"] = kid + signed, err := tok.SignedString(priv) + require.NoError(t, err) + + cfg := config.OIDCConnectConfig{ + ClientID: "client-1", + IssuerURL: "https://issuer.example.com", + JWKSURL: srv.URL, + AllowedSigningAlgs: "RS256", + ClockSkewSeconds: 120, + } + + parsed, err := oidcParseAndValidateIDToken(context.Background(), cfg, signed, "nonce-ok") + require.NoError(t, err) + require.Equal(t, "subject-1", parsed.Subject) + require.Equal(t, "https://issuer.example.com", parsed.Issuer) + + _, err = oidcParseAndValidateIDToken(context.Background(), cfg, signed, "bad-nonce") + require.Error(t, err) +} + +func buildRSAJWK(kid string, pub *rsa.PublicKey) oidcJWK { + n := base64.RawURLEncoding.EncodeToString(pub.N.Bytes()) + e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pub.E)).Bytes()) + return oidcJWK{ + Kty: "RSA", + Kid: kid, + Use: "sig", + Alg: "RS256", + N: n, + E: e, + } +} diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index 2eab670e759605cc31cfa9c75a20b40683e6b8fa..478600eb8c9a9e53c3c909c8c49002786c9b0a24 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -133,16 +133,17 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup { return nil } out := &AdminGroup{ - Group: groupFromServiceBase(g), - ModelRouting: g.ModelRouting, - ModelRoutingEnabled: g.ModelRoutingEnabled, - MCPXMLInject: g.MCPXMLInject, - DefaultMappedModel: g.DefaultMappedModel, - SupportedModelScopes: g.SupportedModelScopes, - AccountCount: g.AccountCount, - ActiveAccountCount: g.ActiveAccountCount, - RateLimitedAccountCount: g.RateLimitedAccountCount, - SortOrder: g.SortOrder, + Group: groupFromServiceBase(g), + ModelRouting: g.ModelRouting, + ModelRoutingEnabled: g.ModelRoutingEnabled, + MCPXMLInject: g.MCPXMLInject, + DefaultMappedModel: g.DefaultMappedModel, + MessagesDispatchModelConfig: g.MessagesDispatchModelConfig, + SupportedModelScopes: g.SupportedModelScopes, + AccountCount: g.AccountCount, + ActiveAccountCount: g.ActiveAccountCount, + RateLimitedAccountCount: g.RateLimitedAccountCount, + SortOrder: g.SortOrder, } if len(g.AccountGroups) > 0 { out.AccountGroups = make([]AccountGroup, 0, len(g.AccountGroups)) diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index 73707f79a57d5af4e698718c50e78fd87e7efb40..cbbe92160d3930bb08ef91f84b48dbfb848fbb38 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -51,6 +51,29 @@ type SystemSettings struct { LinuxDoConnectClientSecretConfigured bool `json:"linuxdo_connect_client_secret_configured"` LinuxDoConnectRedirectURL string `json:"linuxdo_connect_redirect_url"` + OIDCConnectEnabled bool `json:"oidc_connect_enabled"` + OIDCConnectProviderName string `json:"oidc_connect_provider_name"` + OIDCConnectClientID string `json:"oidc_connect_client_id"` + OIDCConnectClientSecretConfigured bool `json:"oidc_connect_client_secret_configured"` + OIDCConnectIssuerURL string `json:"oidc_connect_issuer_url"` + OIDCConnectDiscoveryURL string `json:"oidc_connect_discovery_url"` + OIDCConnectAuthorizeURL string `json:"oidc_connect_authorize_url"` + OIDCConnectTokenURL string `json:"oidc_connect_token_url"` + OIDCConnectUserInfoURL string `json:"oidc_connect_userinfo_url"` + OIDCConnectJWKSURL string `json:"oidc_connect_jwks_url"` + OIDCConnectScopes string `json:"oidc_connect_scopes"` + OIDCConnectRedirectURL string `json:"oidc_connect_redirect_url"` + OIDCConnectFrontendRedirectURL string `json:"oidc_connect_frontend_redirect_url"` + OIDCConnectTokenAuthMethod string `json:"oidc_connect_token_auth_method"` + OIDCConnectUsePKCE bool `json:"oidc_connect_use_pkce"` + OIDCConnectValidateIDToken bool `json:"oidc_connect_validate_id_token"` + OIDCConnectAllowedSigningAlgs string `json:"oidc_connect_allowed_signing_algs"` + OIDCConnectClockSkewSeconds int `json:"oidc_connect_clock_skew_seconds"` + OIDCConnectRequireEmailVerified bool `json:"oidc_connect_require_email_verified"` + OIDCConnectUserInfoEmailPath string `json:"oidc_connect_userinfo_email_path"` + OIDCConnectUserInfoIDPath string `json:"oidc_connect_userinfo_id_path"` + OIDCConnectUserInfoUsernamePath string `json:"oidc_connect_userinfo_username_path"` + SiteName string `json:"site_name"` SiteLogo string `json:"site_logo"` SiteSubtitle string `json:"site_subtitle"` @@ -61,6 +84,8 @@ type SystemSettings struct { HideCcsImportButton bool `json:"hide_ccs_import_button"` PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"` PurchaseSubscriptionURL string `json:"purchase_subscription_url"` + TableDefaultPageSize int `json:"table_default_page_size"` + TablePageSizeOptions []int `json:"table_page_size_options"` CustomMenuItems []CustomMenuItem `json:"custom_menu_items"` CustomEndpoints []CustomEndpoint `json:"custom_endpoints"` @@ -98,6 +123,28 @@ type SystemSettings struct { EnableFingerprintUnification bool `json:"enable_fingerprint_unification"` EnableMetadataPassthrough bool `json:"enable_metadata_passthrough"` EnableCCHSigning bool `json:"enable_cch_signing"` + + // Payment configuration + PaymentEnabled bool `json:"payment_enabled"` + PaymentMinAmount float64 `json:"payment_min_amount"` + PaymentMaxAmount float64 `json:"payment_max_amount"` + PaymentDailyLimit float64 `json:"payment_daily_limit"` + PaymentOrderTimeoutMin int `json:"payment_order_timeout_minutes"` + PaymentMaxPendingOrders int `json:"payment_max_pending_orders"` + PaymentEnabledTypes []string `json:"payment_enabled_types"` + PaymentBalanceDisabled bool `json:"payment_balance_disabled"` + PaymentLoadBalanceStrat string `json:"payment_load_balance_strategy"` + PaymentProductNamePrefix string `json:"payment_product_name_prefix"` + PaymentProductNameSuffix string `json:"payment_product_name_suffix"` + PaymentHelpImageURL string `json:"payment_help_image_url"` + PaymentHelpText string `json:"payment_help_text"` + + // Cancel rate limit + PaymentCancelRateLimitEnabled bool `json:"payment_cancel_rate_limit_enabled"` + PaymentCancelRateLimitMax int `json:"payment_cancel_rate_limit_max"` + PaymentCancelRateLimitWindow int `json:"payment_cancel_rate_limit_window"` + PaymentCancelRateLimitUnit string `json:"payment_cancel_rate_limit_unit"` + PaymentCancelRateLimitMode string `json:"payment_cancel_rate_limit_window_mode"` } type DefaultSubscriptionSetting struct { @@ -125,10 +172,16 @@ type PublicSettings struct { HideCcsImportButton bool `json:"hide_ccs_import_button"` PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"` PurchaseSubscriptionURL string `json:"purchase_subscription_url"` + TableDefaultPageSize int `json:"table_default_page_size"` + TablePageSizeOptions []int `json:"table_page_size_options"` CustomMenuItems []CustomMenuItem `json:"custom_menu_items"` CustomEndpoints []CustomEndpoint `json:"custom_endpoints"` LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"` + OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"` + OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"` + SoraClientEnabled bool `json:"sora_client_enabled"` BackendModeEnabled bool `json:"backend_mode_enabled"` + PaymentEnabled bool `json:"payment_enabled"` Version string `json:"version"` } diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index 82065deb724da9cc0e5cbe8f7fb5025c274234e8..e026ca655180d1577045d41ea4b41667f9deee1c 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -1,6 +1,10 @@ package dto -import "time" +import ( + "time" + + "github.com/Wei-Shaw/sub2api/internal/domain" +) type User struct { ID int64 `json:"id"` @@ -112,7 +116,8 @@ type AdminGroup struct { MCPXMLInject bool `json:"mcp_xml_inject"` // OpenAI Messages 调度配置(仅 openai 平台使用) - DefaultMappedModel string `json:"default_mapped_model"` + DefaultMappedModel string `json:"default_mapped_model"` + MessagesDispatchModelConfig domain.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"` // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes []string `json:"supported_model_scopes"` diff --git a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go index 4caef9551ba8d3c5100c6d6baa3c4e954c8179d4..acea37804f4679d4f102c061acf77e71c6e7fe5f 100644 --- a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go +++ b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go @@ -34,7 +34,12 @@ func (f *fakeSchedulerCache) GetSnapshot(_ context.Context, _ service.SchedulerB func (f *fakeSchedulerCache) SetSnapshot(_ context.Context, _ service.SchedulerBucket, _ []service.Account) error { return nil } -func (f *fakeSchedulerCache) GetAccount(_ context.Context, _ int64) (*service.Account, error) { +func (f *fakeSchedulerCache) GetAccount(_ context.Context, id int64) (*service.Account, error) { + for _, account := range f.accounts { + if account != nil && account.ID == id { + return account, nil + } + } return nil, nil } func (f *fakeSchedulerCache) SetAccount(_ context.Context, _ *service.Account) error { return nil } diff --git a/backend/internal/handler/handler.go b/backend/internal/handler/handler.go index d4c349fb6583e03469e17b0cc2967a2f765c528e..906a74f148fef7a37080500a84a6db2a541984d7 100644 --- a/backend/internal/handler/handler.go +++ b/backend/internal/handler/handler.go @@ -31,22 +31,25 @@ type AdminHandlers struct { APIKey *admin.AdminAPIKeyHandler ScheduledTest *admin.ScheduledTestHandler Channel *admin.ChannelHandler + Payment *admin.PaymentHandler } // Handlers contains all HTTP handlers type Handlers struct { - Auth *AuthHandler - User *UserHandler - APIKey *APIKeyHandler - Usage *UsageHandler - Redeem *RedeemHandler - Subscription *SubscriptionHandler - Announcement *AnnouncementHandler - Admin *AdminHandlers - Gateway *GatewayHandler - OpenAIGateway *OpenAIGatewayHandler - Setting *SettingHandler - Totp *TotpHandler + Auth *AuthHandler + User *UserHandler + APIKey *APIKeyHandler + Usage *UsageHandler + Redeem *RedeemHandler + Subscription *SubscriptionHandler + Announcement *AnnouncementHandler + Admin *AdminHandlers + Gateway *GatewayHandler + OpenAIGateway *OpenAIGatewayHandler + Setting *SettingHandler + Totp *TotpHandler + Payment *PaymentHandler + PaymentWebhook *PaymentWebhookHandler } // BuildInfo contains build-time information diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go index 4747ccfe1e19d5c51d021bb08ee2675fd93c904a..5319b55d9dddf85a13200e749ee00a15128f1de1 100644 --- a/backend/internal/handler/openai_gateway_handler.go +++ b/backend/internal/handler/openai_gateway_handler.go @@ -47,6 +47,13 @@ func resolveOpenAIForwardDefaultMappedModel(apiKey *service.APIKey, fallbackMode return strings.TrimSpace(apiKey.Group.DefaultMappedModel) } +func resolveOpenAIMessagesDispatchMappedModel(apiKey *service.APIKey, requestedModel string) string { + if apiKey == nil || apiKey.Group == nil { + return "" + } + return strings.TrimSpace(apiKey.Group.ResolveMessagesDispatchModel(requestedModel)) +} + // NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler func NewOpenAIGatewayHandler( gatewayService *service.OpenAIGatewayService, @@ -551,6 +558,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) { } reqModel := modelResult.String() routingModel := service.NormalizeOpenAICompatRequestedModel(reqModel) + preferredMappedModel := resolveOpenAIMessagesDispatchMappedModel(apiKey, reqModel) reqStream := gjson.GetBytes(body, "stream").Bool() reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream)) @@ -609,17 +617,20 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) { failedAccountIDs := make(map[int64]struct{}) sameAccountRetryCount := make(map[int64]int) var lastFailoverErr *service.UpstreamFailoverError + effectiveMappedModel := preferredMappedModel for { - // 清除上一次迭代的降级模型标记,避免残留影响本次迭代 - c.Set("openai_messages_fallback_model", "") + currentRoutingModel := routingModel + if effectiveMappedModel != "" { + currentRoutingModel = effectiveMappedModel + } reqLog.Debug("openai_messages.account_selecting", zap.Int("excluded_account_count", len(failedAccountIDs))) selection, scheduleDecision, err := h.gatewayService.SelectAccountWithScheduler( c.Request.Context(), apiKey.GroupID, "", // no previous_response_id sessionHash, - routingModel, + currentRoutingModel, failedAccountIDs, service.OpenAIUpstreamTransportAny, ) @@ -628,29 +639,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) { zap.Error(err), zap.Int("excluded_account_count", len(failedAccountIDs)), ) - // 首次调度失败 + 有默认映射模型 → 用默认模型重试 if len(failedAccountIDs) == 0 { - defaultModel := "" - if apiKey.Group != nil { - defaultModel = apiKey.Group.DefaultMappedModel - } - if defaultModel != "" && defaultModel != routingModel { - reqLog.Info("openai_messages.fallback_to_default_model", - zap.String("default_mapped_model", defaultModel), - ) - selection, scheduleDecision, err = h.gatewayService.SelectAccountWithScheduler( - c.Request.Context(), - apiKey.GroupID, - "", - sessionHash, - defaultModel, - failedAccountIDs, - service.OpenAIUpstreamTransportAny, - ) - if err == nil && selection != nil { - c.Set("openai_messages_fallback_model", defaultModel) - } - } if err != nil { h.anthropicStreamingAwareError(c, http.StatusServiceUnavailable, "api_error", "Service temporarily unavailable", streamStarted) return @@ -682,9 +671,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) { service.SetOpsLatencyMs(c, service.OpsRoutingLatencyMsKey, time.Since(routingStart).Milliseconds()) forwardStart := time.Now() - // Forward 层需要始终拿到 group 默认映射模型,这样未命中账号级映射的 - // Claude 兼容模型才不会在后续 Codex 规范化中意外退化到 gpt-5.1。 - defaultMappedModel := resolveOpenAIForwardDefaultMappedModel(apiKey, c.GetString("openai_messages_fallback_model")) + defaultMappedModel := strings.TrimSpace(effectiveMappedModel) // 应用渠道模型映射到请求体 forwardBody := body if channelMappingMsg.Mapped { diff --git a/backend/internal/handler/openai_gateway_handler_test.go b/backend/internal/handler/openai_gateway_handler_test.go index 7bbf94ecb376ab7e18629d492e7917e119ee94e0..d299fb81e338120a49052581b8dd00ac813c6ab1 100644 --- a/backend/internal/handler/openai_gateway_handler_test.go +++ b/backend/internal/handler/openai_gateway_handler_test.go @@ -360,7 +360,7 @@ func TestResolveOpenAIForwardDefaultMappedModel(t *testing.T) { require.Equal(t, "gpt-5.2", resolveOpenAIForwardDefaultMappedModel(apiKey, " gpt-5.2 ")) }) - t.Run("uses_group_default_on_normal_path", func(t *testing.T) { + t.Run("uses_group_default_when_explicit_fallback_absent", func(t *testing.T) { apiKey := &service.APIKey{ Group: &service.Group{DefaultMappedModel: "gpt-5.4"}, } @@ -376,6 +376,45 @@ func TestResolveOpenAIForwardDefaultMappedModel(t *testing.T) { }) } +func TestResolveOpenAIMessagesDispatchMappedModel(t *testing.T) { + t.Run("exact_claude_model_override_wins", func(t *testing.T) { + apiKey := &service.APIKey{ + Group: &service.Group{ + MessagesDispatchModelConfig: service.OpenAIMessagesDispatchModelConfig{ + SonnetMappedModel: "gpt-5.2", + ExactModelMappings: map[string]string{ + "claude-sonnet-4-5-20250929": "gpt-5.4-mini-high", + }, + }, + }, + } + require.Equal(t, "gpt-5.4-mini", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-sonnet-4-5-20250929")) + }) + + t.Run("uses_family_default_when_no_override", func(t *testing.T) { + apiKey := &service.APIKey{Group: &service.Group{}} + require.Equal(t, "gpt-5.4", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-opus-4-6")) + require.Equal(t, "gpt-5.3-codex", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-sonnet-4-5-20250929")) + require.Equal(t, "gpt-5.4-mini", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-haiku-4-5-20251001")) + }) + + t.Run("returns_empty_for_non_claude_or_missing_group", func(t *testing.T) { + require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(nil, "claude-sonnet-4-5-20250929")) + require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(&service.APIKey{}, "claude-sonnet-4-5-20250929")) + require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(&service.APIKey{Group: &service.Group{}}, "gpt-5.4")) + }) + + t.Run("does_not_fall_back_to_group_default_mapped_model", func(t *testing.T) { + apiKey := &service.APIKey{ + Group: &service.Group{ + DefaultMappedModel: "gpt-5.4", + }, + } + require.Empty(t, resolveOpenAIMessagesDispatchMappedModel(apiKey, "gpt-5.4")) + require.Equal(t, "gpt-5.3-codex", resolveOpenAIMessagesDispatchMappedModel(apiKey, "claude-sonnet-4-5-20250929")) + }) +} + func TestOpenAIResponses_MissingDependencies_ReturnsServiceUnavailable(t *testing.T) { gin.SetMode(gin.TestMode) diff --git a/backend/internal/handler/payment_handler.go b/backend/internal/handler/payment_handler.go new file mode 100644 index 0000000000000000000000000000000000000000..0425fc49c7fccf11a0621ebc41b5cb00ee7130e5 --- /dev/null +++ b/backend/internal/handler/payment_handler.go @@ -0,0 +1,421 @@ +package handler + +import ( + "strconv" + "strings" + + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/pkg/response" + middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware" + "github.com/Wei-Shaw/sub2api/internal/service" + + "github.com/gin-gonic/gin" +) + +// PaymentHandler handles user-facing payment requests. +type PaymentHandler struct { + channelService *service.ChannelService + paymentService *service.PaymentService + configService *service.PaymentConfigService +} + +// NewPaymentHandler creates a new PaymentHandler. +func NewPaymentHandler(paymentService *service.PaymentService, configService *service.PaymentConfigService, channelService *service.ChannelService) *PaymentHandler { + return &PaymentHandler{ + channelService: channelService, + paymentService: paymentService, + configService: configService, + } +} + +// GetPaymentConfig returns the payment system configuration. +// GET /api/v1/payment/config +func (h *PaymentHandler) GetPaymentConfig(c *gin.Context) { + cfg, err := h.configService.GetPaymentConfig(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, cfg) +} + +// GetPlans returns subscription plans available for sale. +// GET /api/v1/payment/plans +func (h *PaymentHandler) GetPlans(c *gin.Context) { + plans, err := h.configService.ListPlansForSale(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + // Enrich plans with group platform for frontend color coding + type planWithPlatform struct { + ID int64 `json:"id"` + GroupID int64 `json:"group_id"` + GroupPlatform string `json:"group_platform"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + OriginalPrice *float64 `json:"original_price,omitempty"` + ValidityDays int `json:"validity_days"` + ValidityUnit string `json:"validity_unit"` + Features string `json:"features"` + ProductName string `json:"product_name"` + ForSale bool `json:"for_sale"` + SortOrder int `json:"sort_order"` + } + platformMap := h.configService.GetGroupPlatformMap(c.Request.Context(), plans) + result := make([]planWithPlatform, 0, len(plans)) + for _, p := range plans { + result = append(result, planWithPlatform{ + ID: int64(p.ID), GroupID: p.GroupID, GroupPlatform: platformMap[p.GroupID], + Name: p.Name, Description: p.Description, Price: p.Price, OriginalPrice: p.OriginalPrice, + ValidityDays: p.ValidityDays, ValidityUnit: p.ValidityUnit, Features: p.Features, + ProductName: p.ProductName, ForSale: p.ForSale, SortOrder: p.SortOrder, + }) + } + response.Success(c, result) +} + +// GetChannels returns enabled payment channels. +// GET /api/v1/payment/channels +func (h *PaymentHandler) GetChannels(c *gin.Context) { + channels, _, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{Page: 1, PageSize: 1000}, "active", "") + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, channels) +} + +// GetCheckoutInfo returns all data the payment page needs in a single call: +// payment methods with limits, subscription plans, and configuration. +// GET /api/v1/payment/checkout-info +func (h *PaymentHandler) GetCheckoutInfo(c *gin.Context) { + ctx := c.Request.Context() + + // Fetch limits (methods + global range) + limitsResp, err := h.configService.GetAvailableMethodLimits(ctx) + if err != nil { + response.ErrorFrom(c, err) + return + } + + // Fetch payment config + cfg, err := h.configService.GetPaymentConfig(ctx) + if err != nil { + response.ErrorFrom(c, err) + return + } + + // Fetch plans with group info + plans, _ := h.configService.ListPlansForSale(ctx) + groupInfo := h.configService.GetGroupInfoMap(ctx, plans) + planList := make([]checkoutPlan, 0, len(plans)) + for _, p := range plans { + gi := groupInfo[p.GroupID] + planList = append(planList, checkoutPlan{ + ID: int64(p.ID), GroupID: p.GroupID, + GroupPlatform: gi.Platform, GroupName: gi.Name, + RateMultiplier: gi.RateMultiplier, DailyLimitUSD: gi.DailyLimitUSD, + WeeklyLimitUSD: gi.WeeklyLimitUSD, MonthlyLimitUSD: gi.MonthlyLimitUSD, + ModelScopes: gi.ModelScopes, + Name: p.Name, Description: p.Description, Price: p.Price, OriginalPrice: p.OriginalPrice, + ValidityDays: p.ValidityDays, ValidityUnit: p.ValidityUnit, Features: parseFeatures(p.Features), + ProductName: p.ProductName, + }) + } + + response.Success(c, checkoutInfoResponse{ + Methods: limitsResp.Methods, + GlobalMin: limitsResp.GlobalMin, + GlobalMax: limitsResp.GlobalMax, + Plans: planList, + BalanceDisabled: cfg.BalanceDisabled, + HelpText: cfg.HelpText, + HelpImageURL: cfg.HelpImageURL, + StripePublishableKey: cfg.StripePublishableKey, + }) +} + +type checkoutInfoResponse struct { + Methods map[string]service.MethodLimits `json:"methods"` + GlobalMin float64 `json:"global_min"` + GlobalMax float64 `json:"global_max"` + Plans []checkoutPlan `json:"plans"` + BalanceDisabled bool `json:"balance_disabled"` + HelpText string `json:"help_text"` + HelpImageURL string `json:"help_image_url"` + StripePublishableKey string `json:"stripe_publishable_key"` +} + +type checkoutPlan struct { + ID int64 `json:"id"` + GroupID int64 `json:"group_id"` + GroupPlatform string `json:"group_platform"` + GroupName string `json:"group_name"` + RateMultiplier float64 `json:"rate_multiplier"` + DailyLimitUSD *float64 `json:"daily_limit_usd"` + WeeklyLimitUSD *float64 `json:"weekly_limit_usd"` + MonthlyLimitUSD *float64 `json:"monthly_limit_usd"` + ModelScopes []string `json:"supported_model_scopes"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + OriginalPrice *float64 `json:"original_price,omitempty"` + ValidityDays int `json:"validity_days"` + ValidityUnit string `json:"validity_unit"` + Features []string `json:"features"` + ProductName string `json:"product_name"` +} + +// parseFeatures splits a newline-separated features string into a string slice. +func parseFeatures(raw string) []string { + if raw == "" { + return []string{} + } + var out []string + for _, line := range strings.Split(raw, "\n") { + if s := strings.TrimSpace(line); s != "" { + out = append(out, s) + } + } + if out == nil { + return []string{} + } + return out +} + +// GetLimits returns per-payment-type limits derived from enabled provider instances. +// GET /api/v1/payment/limits +func (h *PaymentHandler) GetLimits(c *gin.Context) { + resp, err := h.configService.GetAvailableMethodLimits(c.Request.Context()) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, resp) +} + +// CreateOrderRequest is the request body for creating a payment order. +type CreateOrderRequest struct { + Amount float64 `json:"amount"` + PaymentType string `json:"payment_type" binding:"required"` + OrderType string `json:"order_type"` + PlanID int64 `json:"plan_id"` +} + +// CreateOrder creates a new payment order. +// POST /api/v1/payment/orders +func (h *PaymentHandler) CreateOrder(c *gin.Context) { + subject, ok := requireAuth(c) + if !ok { + return + } + + var req CreateOrderRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + + result, err := h.paymentService.CreateOrder(c.Request.Context(), service.CreateOrderRequest{ + UserID: subject.UserID, + Amount: req.Amount, + PaymentType: req.PaymentType, + ClientIP: c.ClientIP(), + IsMobile: isMobile(c), + SrcHost: c.Request.Host, + SrcURL: c.Request.Referer(), + OrderType: req.OrderType, + PlanID: req.PlanID, + }) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, result) +} + +// GetMyOrders returns the authenticated user's orders. +// GET /api/v1/payment/orders/my +func (h *PaymentHandler) GetMyOrders(c *gin.Context) { + subject, ok := requireAuth(c) + if !ok { + return + } + + page, pageSize := response.ParsePagination(c) + orders, total, err := h.paymentService.GetUserOrders(c.Request.Context(), subject.UserID, service.OrderListParams{ + Page: page, + PageSize: pageSize, + Status: c.Query("status"), + OrderType: c.Query("order_type"), + PaymentType: c.Query("payment_type"), + }) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Paginated(c, orders, int64(total), page, pageSize) +} + +// GetOrder returns a single order for the authenticated user. +// GET /api/v1/payment/orders/:id +func (h *PaymentHandler) GetOrder(c *gin.Context) { + subject, ok := requireAuth(c) + if !ok { + return + } + + orderID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid order ID") + return + } + + order, err := h.paymentService.GetOrder(c.Request.Context(), orderID, subject.UserID) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, order) +} + +// CancelOrder cancels a pending order for the authenticated user. +// POST /api/v1/payment/orders/:id/cancel +func (h *PaymentHandler) CancelOrder(c *gin.Context) { + subject, ok := requireAuth(c) + if !ok { + return + } + + orderID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid order ID") + return + } + + msg, err := h.paymentService.CancelOrder(c.Request.Context(), orderID, subject.UserID) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, gin.H{"message": msg}) +} + +// RefundRequestBody is the request body for requesting a refund. +type RefundRequestBody struct { + Reason string `json:"reason"` +} + +// RequestRefund submits a refund request for a completed order. +// POST /api/v1/payment/orders/:id/refund-request +func (h *PaymentHandler) RequestRefund(c *gin.Context) { + subject, ok := requireAuth(c) + if !ok { + return + } + + orderID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid order ID") + return + } + + var req RefundRequestBody + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + + if err := h.paymentService.RequestRefund(c.Request.Context(), orderID, subject.UserID, req.Reason); err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, gin.H{"message": "refund requested"}) +} + +// VerifyOrderRequest is the request body for verifying a payment order. +type VerifyOrderRequest struct { + OutTradeNo string `json:"out_trade_no" binding:"required"` +} + +// VerifyOrder actively queries the upstream payment provider to check +// if payment was made, and processes it if so. +// POST /api/v1/payment/orders/verify +func (h *PaymentHandler) VerifyOrder(c *gin.Context) { + subject, ok := requireAuth(c) + if !ok { + return + } + + var req VerifyOrderRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + + order, err := h.paymentService.VerifyOrderByOutTradeNo(c.Request.Context(), req.OutTradeNo, subject.UserID) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, order) +} + +// PublicOrderResult is the limited order info returned by the public verify endpoint. +// No user details are exposed — only payment status information. +type PublicOrderResult struct { + ID int64 `json:"id"` + OutTradeNo string `json:"out_trade_no"` + Amount float64 `json:"amount"` + PayAmount float64 `json:"pay_amount"` + PaymentType string `json:"payment_type"` + Status string `json:"status"` +} + +// VerifyOrderPublic verifies payment status without requiring authentication. +// Returns limited order info (no user details) to prevent information leakage. +// POST /api/v1/payment/public/orders/verify +func (h *PaymentHandler) VerifyOrderPublic(c *gin.Context) { + var req VerifyOrderRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "Invalid request: "+err.Error()) + return + } + order, err := h.paymentService.VerifyOrderPublic(c.Request.Context(), req.OutTradeNo) + if err != nil { + response.ErrorFrom(c, err) + return + } + response.Success(c, PublicOrderResult{ + ID: order.ID, + OutTradeNo: order.OutTradeNo, + Amount: order.Amount, + PayAmount: order.PayAmount, + PaymentType: order.PaymentType, + Status: order.Status, + }) +} + +// requireAuth extracts the authenticated subject from the context. +// Returns the subject and true on success; on failure it writes an Unauthorized response and returns false. +func requireAuth(c *gin.Context) (middleware2.AuthSubject, bool) { + subject, ok := middleware2.GetAuthSubjectFromContext(c) + if !ok { + response.Unauthorized(c, "User not authenticated") + return middleware2.AuthSubject{}, false + } + return subject, true +} + +// isMobile detects mobile user agents. +func isMobile(c *gin.Context) bool { + ua := strings.ToLower(c.GetHeader("User-Agent")) + for _, kw := range []string{"mobile", "android", "iphone", "ipad", "ipod"} { + if strings.Contains(ua, kw) { + return true + } + } + return false +} diff --git a/backend/internal/handler/payment_webhook_handler.go b/backend/internal/handler/payment_webhook_handler.go new file mode 100644 index 0000000000000000000000000000000000000000..8a83bfebb72830b9957c83f4cf419814ffbfc2a0 --- /dev/null +++ b/backend/internal/handler/payment_webhook_handler.go @@ -0,0 +1,158 @@ +package handler + +import ( + "io" + "log/slog" + "net/http" + "net/url" + "strings" + + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/Wei-Shaw/sub2api/internal/service" + + "github.com/gin-gonic/gin" +) + +// PaymentWebhookHandler handles payment provider webhook callbacks. +type PaymentWebhookHandler struct { + paymentService *service.PaymentService + registry *payment.Registry +} + +// maxWebhookBodySize is the maximum allowed webhook request body size (1 MB). +const maxWebhookBodySize = 1 << 20 + +// webhookLogTruncateLen is the maximum length of raw body logged on verify failure. +const webhookLogTruncateLen = 200 + +// NewPaymentWebhookHandler creates a new PaymentWebhookHandler. +func NewPaymentWebhookHandler(paymentService *service.PaymentService, registry *payment.Registry) *PaymentWebhookHandler { + return &PaymentWebhookHandler{ + paymentService: paymentService, + registry: registry, + } +} + +// EasyPayNotify handles EasyPay payment notifications. +// POST /api/v1/payment/webhook/easypay +func (h *PaymentWebhookHandler) EasyPayNotify(c *gin.Context) { + h.handleNotify(c, payment.TypeEasyPay) +} + +// AlipayNotify handles Alipay payment notifications. +// POST /api/v1/payment/webhook/alipay +func (h *PaymentWebhookHandler) AlipayNotify(c *gin.Context) { + h.handleNotify(c, payment.TypeAlipay) +} + +// WxpayNotify handles WeChat Pay payment notifications. +// POST /api/v1/payment/webhook/wxpay +func (h *PaymentWebhookHandler) WxpayNotify(c *gin.Context) { + h.handleNotify(c, payment.TypeWxpay) +} + +// StripeWebhook handles Stripe webhook events. +// POST /api/v1/payment/webhook/stripe +func (h *PaymentWebhookHandler) StripeWebhook(c *gin.Context) { + h.handleNotify(c, payment.TypeStripe) +} + +// handleNotify is the shared logic for all provider webhook handlers. +func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string) { + var rawBody string + if c.Request.Method == http.MethodGet { + // GET callbacks (e.g. EasyPay) pass params as URL query string + rawBody = c.Request.URL.RawQuery + } else { + body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxWebhookBodySize)) + if err != nil { + slog.Error("[Payment Webhook] failed to read body", "provider", providerKey, "error", err) + c.String(http.StatusBadRequest, "failed to read body") + return + } + rawBody = string(body) + } + + // Extract out_trade_no to look up the order's specific provider instance. + // This is needed when multiple instances of the same provider exist (e.g. multiple EasyPay accounts). + outTradeNo := extractOutTradeNo(rawBody, providerKey) + + provider, err := h.paymentService.GetWebhookProvider(c.Request.Context(), providerKey, outTradeNo) + if err != nil { + slog.Warn("[Payment Webhook] provider not found", "provider", providerKey, "outTradeNo", outTradeNo, "error", err) + writeSuccessResponse(c, providerKey) + return + } + + headers := make(map[string]string) + for k := range c.Request.Header { + headers[strings.ToLower(k)] = c.GetHeader(k) + } + + notification, err := provider.VerifyNotification(c.Request.Context(), rawBody, headers) + if err != nil { + truncatedBody := rawBody + if len(truncatedBody) > webhookLogTruncateLen { + truncatedBody = truncatedBody[:webhookLogTruncateLen] + "...(truncated)" + } + slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err, "method", c.Request.Method, "bodyLen", len(rawBody)) + slog.Debug("[Payment Webhook] verify failed body", "provider", providerKey, "rawBody", truncatedBody) + c.String(http.StatusBadRequest, "verify failed") + return + } + + // nil notification means irrelevant event (e.g. Stripe non-payment event); return success. + if notification == nil { + writeSuccessResponse(c, providerKey) + return + } + + if err := h.paymentService.HandlePaymentNotification(c.Request.Context(), notification, providerKey); err != nil { + slog.Error("[Payment Webhook] handle notification failed", "provider", providerKey, "error", err) + c.String(http.StatusInternalServerError, "handle failed") + return + } + + writeSuccessResponse(c, providerKey) +} + +// extractOutTradeNo parses the webhook body to find the out_trade_no. +// This allows looking up the correct provider instance before verification. +func extractOutTradeNo(rawBody, providerKey string) string { + switch providerKey { + case payment.TypeEasyPay: + values, err := url.ParseQuery(rawBody) + if err == nil { + return values.Get("out_trade_no") + } + } + // For other providers (Stripe, Alipay direct, WxPay direct), the registry + // typically has only one instance, so no instance lookup is needed. + return "" +} + +// wxpaySuccessResponse is the JSON response expected by WeChat Pay webhook. +type wxpaySuccessResponse struct { + Code string `json:"code"` + Message string `json:"message"` +} + +// WeChat Pay webhook success response constants. +const ( + wxpaySuccessCode = "SUCCESS" + wxpaySuccessMessage = "成功" +) + +// writeSuccessResponse sends the provider-specific success response. +// WeChat Pay requires JSON {"code":"SUCCESS","message":"成功"}; +// Stripe expects an empty 200; others accept plain text "success". +func writeSuccessResponse(c *gin.Context, providerKey string) { + switch providerKey { + case payment.TypeWxpay: + c.JSON(http.StatusOK, wxpaySuccessResponse{Code: wxpaySuccessCode, Message: wxpaySuccessMessage}) + case payment.TypeStripe: + c.String(http.StatusOK, "") + default: + c.String(http.StatusOK, "success") + } +} diff --git a/backend/internal/handler/payment_webhook_handler_test.go b/backend/internal/handler/payment_webhook_handler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bdef1766d91108a7ab4656bfc29715667a3a40aa --- /dev/null +++ b/backend/internal/handler/payment_webhook_handler_test.go @@ -0,0 +1,99 @@ +//go:build unit + +package handler + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWriteSuccessResponse(t *testing.T) { + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + providerKey string + wantCode int + wantContentType string + wantBody string + checkJSON bool + wantJSONCode string + wantJSONMessage string + }{ + { + name: "wxpay returns JSON with code SUCCESS", + providerKey: "wxpay", + wantCode: http.StatusOK, + wantContentType: "application/json", + checkJSON: true, + wantJSONCode: "SUCCESS", + wantJSONMessage: "成功", + }, + { + name: "stripe returns empty 200", + providerKey: "stripe", + wantCode: http.StatusOK, + wantContentType: "text/plain", + wantBody: "", + }, + { + name: "easypay returns plain text success", + providerKey: "easypay", + wantCode: http.StatusOK, + wantContentType: "text/plain", + wantBody: "success", + }, + { + name: "alipay returns plain text success", + providerKey: "alipay", + wantCode: http.StatusOK, + wantContentType: "text/plain", + wantBody: "success", + }, + { + name: "unknown provider returns plain text success", + providerKey: "unknown_provider", + wantCode: http.StatusOK, + wantContentType: "text/plain", + wantBody: "success", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + writeSuccessResponse(c, tt.providerKey) + + assert.Equal(t, tt.wantCode, w.Code) + assert.Contains(t, w.Header().Get("Content-Type"), tt.wantContentType) + + if tt.checkJSON { + var resp wxpaySuccessResponse + err := json.Unmarshal(w.Body.Bytes(), &resp) + require.NoError(t, err, "response body should be valid JSON") + assert.Equal(t, tt.wantJSONCode, resp.Code) + assert.Equal(t, tt.wantJSONMessage, resp.Message) + } else { + assert.Equal(t, tt.wantBody, w.Body.String()) + } + }) + } +} + +func TestWebhookConstants(t *testing.T) { + t.Run("maxWebhookBodySize is 1MB", func(t *testing.T) { + assert.Equal(t, int64(1<<20), int64(maxWebhookBodySize)) + }) + + t.Run("webhookLogTruncateLen is 200", func(t *testing.T) { + assert.Equal(t, 200, webhookLogTruncateLen) + }) +} diff --git a/backend/internal/handler/setting_handler.go b/backend/internal/handler/setting_handler.go index 977c2301bbf078b093a100b1b47d195121443c96..54a92a8c78592c10a05ea16d34fc22d4a9c5b3e1 100644 --- a/backend/internal/handler/setting_handler.go +++ b/backend/internal/handler/setting_handler.go @@ -51,10 +51,15 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) { HideCcsImportButton: settings.HideCcsImportButton, PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled, PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL, + TableDefaultPageSize: settings.TableDefaultPageSize, + TablePageSizeOptions: settings.TablePageSizeOptions, CustomMenuItems: dto.ParseUserVisibleMenuItems(settings.CustomMenuItems), CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints), LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled, + OIDCOAuthEnabled: settings.OIDCOAuthEnabled, + OIDCOAuthProviderName: settings.OIDCOAuthProviderName, BackendModeEnabled: settings.BackendModeEnabled, + PaymentEnabled: settings.PaymentEnabled, Version: h.version, }) } diff --git a/backend/internal/handler/usage_handler.go b/backend/internal/handler/usage_handler.go index 483f51059b764f8a7eacd14fb35bfcabd51b5e4d..b8506154365bf6643a8a57761bae93e7f13bf1cd 100644 --- a/backend/internal/handler/usage_handler.go +++ b/backend/internal/handler/usage_handler.go @@ -119,7 +119,12 @@ func (h *UsageHandler) List(c *gin.Context) { endTime = &t } - params := pagination.PaginationParams{Page: page, PageSize: pageSize} + params := pagination.PaginationParams{ + Page: page, + PageSize: pageSize, + SortBy: c.DefaultQuery("sort_by", "created_at"), + SortOrder: c.DefaultQuery("sort_order", "desc"), + } filters := usagestats.UsageLogFilters{ UserID: subject.UserID, // Always filter by current user for security APIKeyID: apiKeyID, diff --git a/backend/internal/handler/usage_handler_request_type_test.go b/backend/internal/handler/usage_handler_request_type_test.go index 7c4c79135a6976bf706e9342f556a7bdc4176851..b49ed59ba36b974dee684854f194504166ada056 100644 --- a/backend/internal/handler/usage_handler_request_type_test.go +++ b/backend/internal/handler/usage_handler_request_type_test.go @@ -16,10 +16,12 @@ import ( type userUsageRepoCapture struct { service.UsageLogRepository + listParams pagination.PaginationParams listFilters usagestats.UsageLogFilters } func (s *userUsageRepoCapture) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]service.UsageLog, *pagination.PaginationResult, error) { + s.listParams = params s.listFilters = filters return []service.UsageLog{}, &pagination.PaginationResult{ Total: 0, diff --git a/backend/internal/handler/usage_handler_sort_test.go b/backend/internal/handler/usage_handler_sort_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1af313b09d295223cd7ca7b07278bc5de3d7bc26 --- /dev/null +++ b/backend/internal/handler/usage_handler_sort_test.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUserUsageListSortParams(t *testing.T) { + repo := &userUsageRepoCapture{} + router := newUserUsageRequestTypeTestRouter(repo) + + req := httptest.NewRequest(http.MethodGet, "/usage?sort_by=model&sort_order=ASC", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "model", repo.listParams.SortBy) + require.Equal(t, "ASC", repo.listParams.SortOrder) +} + +func TestUserUsageListSortDefaults(t *testing.T) { + repo := &userUsageRepoCapture{} + router := newUserUsageRequestTypeTestRouter(repo) + + req := httptest.NewRequest(http.MethodGet, "/usage", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, "created_at", repo.listParams.SortBy) + require.Equal(t, "desc", repo.listParams.SortOrder) +} diff --git a/backend/internal/handler/wire.go b/backend/internal/handler/wire.go index d9622594f2c784d88c721d19bbc439d360e0b481..4b54d41ad32c28cc79582eb458459031da55c875 100644 --- a/backend/internal/handler/wire.go +++ b/backend/internal/handler/wire.go @@ -34,6 +34,7 @@ func ProvideAdminHandlers( apiKeyHandler *admin.AdminAPIKeyHandler, scheduledTestHandler *admin.ScheduledTestHandler, channelHandler *admin.ChannelHandler, + paymentHandler *admin.PaymentHandler, ) *AdminHandlers { return &AdminHandlers{ Dashboard: dashboardHandler, @@ -61,6 +62,7 @@ func ProvideAdminHandlers( APIKey: apiKeyHandler, ScheduledTest: scheduledTestHandler, Channel: channelHandler, + Payment: paymentHandler, } } @@ -88,22 +90,26 @@ func ProvideHandlers( openaiGatewayHandler *OpenAIGatewayHandler, settingHandler *SettingHandler, totpHandler *TotpHandler, + paymentHandler *PaymentHandler, + paymentWebhookHandler *PaymentWebhookHandler, _ *service.IdempotencyCoordinator, _ *service.IdempotencyCleanupService, ) *Handlers { return &Handlers{ - Auth: authHandler, - User: userHandler, - APIKey: apiKeyHandler, - Usage: usageHandler, - Redeem: redeemHandler, - Subscription: subscriptionHandler, - Announcement: announcementHandler, - Admin: adminHandlers, - Gateway: gatewayHandler, - OpenAIGateway: openaiGatewayHandler, - Setting: settingHandler, - Totp: totpHandler, + Auth: authHandler, + User: userHandler, + APIKey: apiKeyHandler, + Usage: usageHandler, + Redeem: redeemHandler, + Subscription: subscriptionHandler, + Announcement: announcementHandler, + Admin: adminHandlers, + Gateway: gatewayHandler, + OpenAIGateway: openaiGatewayHandler, + Setting: settingHandler, + Totp: totpHandler, + Payment: paymentHandler, + PaymentWebhook: paymentWebhookHandler, } } @@ -121,6 +127,8 @@ var ProviderSet = wire.NewSet( NewOpenAIGatewayHandler, NewTotpHandler, ProvideSettingHandler, + NewPaymentHandler, + NewPaymentWebhookHandler, // Admin handlers admin.NewDashboardHandler, @@ -148,6 +156,7 @@ var ProviderSet = wire.NewSet( admin.NewAdminAPIKeyHandler, admin.NewScheduledTestHandler, admin.NewChannelHandler, + admin.NewPaymentHandler, // AdminHandlers and Handlers constructors ProvideAdminHandlers, diff --git a/backend/internal/payment/amount.go b/backend/internal/payment/amount.go new file mode 100644 index 0000000000000000000000000000000000000000..8489ffa326533568f665cd19e899560658600da5 --- /dev/null +++ b/backend/internal/payment/amount.go @@ -0,0 +1,24 @@ +package payment + +import ( + "fmt" + + "github.com/shopspring/decimal" +) + +const centsPerYuan = 100 + +// YuanToFen converts a CNY yuan string (e.g. "10.50") to fen (int64). +// Uses shopspring/decimal for precision. +func YuanToFen(yuanStr string) (int64, error) { + d, err := decimal.NewFromString(yuanStr) + if err != nil { + return 0, fmt.Errorf("invalid amount: %s", yuanStr) + } + return d.Mul(decimal.NewFromInt(centsPerYuan)).IntPart(), nil +} + +// FenToYuan converts fen (int64) to yuan as a float64 for interface compatibility. +func FenToYuan(fen int64) float64 { + return decimal.NewFromInt(fen).Div(decimal.NewFromInt(centsPerYuan)).InexactFloat64() +} diff --git a/backend/internal/payment/amount_test.go b/backend/internal/payment/amount_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6120b1894add46046d573651f55900d116b0c57a --- /dev/null +++ b/backend/internal/payment/amount_test.go @@ -0,0 +1,128 @@ +//go:build unit + +package payment + +import ( + "math" + "testing" +) + +func TestYuanToFen(t *testing.T) { + tests := []struct { + name string + input string + want int64 + wantErr bool + }{ + // Normal values + {name: "one yuan", input: "1.00", want: 100}, + {name: "ten yuan fifty fen", input: "10.50", want: 1050}, + {name: "one fen", input: "0.01", want: 1}, + {name: "large amount", input: "99999.99", want: 9999999}, + + // Edge: zero + {name: "zero no decimal", input: "0", want: 0}, + {name: "zero with decimal", input: "0.00", want: 0}, + + // IEEE 754 precision edge case: 1.15 * 100 = 114.99999... in float64 + {name: "ieee754 precision 1.15", input: "1.15", want: 115}, + + // More precision edge cases + {name: "ieee754 precision 0.1", input: "0.1", want: 10}, + {name: "ieee754 precision 0.2", input: "0.2", want: 20}, + {name: "ieee754 precision 33.33", input: "33.33", want: 3333}, + + // Large value + {name: "hundred thousand", input: "100000.00", want: 10000000}, + + // Integer without decimal + {name: "integer 5", input: "5", want: 500}, + {name: "integer 100", input: "100", want: 10000}, + + // Single decimal place + {name: "single decimal 1.5", input: "1.5", want: 150}, + + // Negative values + {name: "negative one yuan", input: "-1.00", want: -100}, + {name: "negative with fen", input: "-10.50", want: -1050}, + + // Invalid inputs + {name: "empty string", input: "", wantErr: true}, + {name: "alphabetic", input: "abc", wantErr: true}, + {name: "double dot", input: "1.2.3", wantErr: true}, + {name: "spaces", input: " ", wantErr: true}, + {name: "special chars", input: "$10.00", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := YuanToFen(tt.input) + if tt.wantErr { + if err == nil { + t.Errorf("YuanToFen(%q) expected error, got %d", tt.input, got) + } + return + } + if err != nil { + t.Fatalf("YuanToFen(%q) unexpected error: %v", tt.input, err) + } + if got != tt.want { + t.Errorf("YuanToFen(%q) = %d, want %d", tt.input, got, tt.want) + } + }) + } +} + +func TestFenToYuan(t *testing.T) { + tests := []struct { + name string + fen int64 + want float64 + }{ + {name: "one yuan", fen: 100, want: 1.0}, + {name: "ten yuan fifty fen", fen: 1050, want: 10.5}, + {name: "one fen", fen: 1, want: 0.01}, + {name: "zero", fen: 0, want: 0.0}, + {name: "large amount", fen: 9999999, want: 99999.99}, + {name: "negative", fen: -100, want: -1.0}, + {name: "negative with fen", fen: -1050, want: -10.5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := FenToYuan(tt.fen) + if math.Abs(got-tt.want) > 1e-9 { + t.Errorf("FenToYuan(%d) = %f, want %f", tt.fen, got, tt.want) + } + }) + } +} + +func TestYuanToFenRoundTrip(t *testing.T) { + // Verify that converting yuan->fen->yuan preserves the value. + cases := []struct { + yuan string + fen int64 + }{ + {"0.01", 1}, + {"1.00", 100}, + {"10.50", 1050}, + {"99999.99", 9999999}, + } + + for _, tc := range cases { + fen, err := YuanToFen(tc.yuan) + if err != nil { + t.Fatalf("YuanToFen(%q) unexpected error: %v", tc.yuan, err) + } + if fen != tc.fen { + t.Errorf("YuanToFen(%q) = %d, want %d", tc.yuan, fen, tc.fen) + } + yuan := FenToYuan(fen) + // Parse expected yuan back for comparison + expectedYuan := FenToYuan(tc.fen) + if math.Abs(yuan-expectedYuan) > 1e-9 { + t.Errorf("round-trip: FenToYuan(%d) = %f, want %f", fen, yuan, expectedYuan) + } + } +} diff --git a/backend/internal/payment/crypto.go b/backend/internal/payment/crypto.go new file mode 100644 index 0000000000000000000000000000000000000000..e39e957f4d29e2efd1dad9ce5e709f76764f0454 --- /dev/null +++ b/backend/internal/payment/crypto.go @@ -0,0 +1,98 @@ +package payment + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + "strings" +) + +// Encrypt encrypts plaintext using AES-256-GCM with the given 32-byte key. +// The output format is "iv:authTag:ciphertext" where each component is base64-encoded, +// matching the Node.js crypto.ts format for cross-compatibility. +func Encrypt(plaintext string, key []byte) (string, error) { + if len(key) != 32 { + return "", fmt.Errorf("encryption key must be 32 bytes, got %d", len(key)) + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", fmt.Errorf("create AES cipher: %w", err) + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("create GCM: %w", err) + } + + nonce := make([]byte, gcm.NonceSize()) // 12 bytes for GCM + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", fmt.Errorf("generate nonce: %w", err) + } + + // Seal appends the ciphertext + auth tag + sealed := gcm.Seal(nil, nonce, []byte(plaintext), nil) + + // Split sealed into ciphertext and auth tag (last 16 bytes) + tagSize := gcm.Overhead() + ciphertext := sealed[:len(sealed)-tagSize] + authTag := sealed[len(sealed)-tagSize:] + + // Format: iv:authTag:ciphertext (all base64) + return fmt.Sprintf("%s:%s:%s", + base64.StdEncoding.EncodeToString(nonce), + base64.StdEncoding.EncodeToString(authTag), + base64.StdEncoding.EncodeToString(ciphertext), + ), nil +} + +// Decrypt decrypts a ciphertext string produced by Encrypt. +// The input format is "iv:authTag:ciphertext" where each component is base64-encoded. +func Decrypt(ciphertext string, key []byte) (string, error) { + if len(key) != 32 { + return "", fmt.Errorf("encryption key must be 32 bytes, got %d", len(key)) + } + + parts := strings.SplitN(ciphertext, ":", 3) + if len(parts) != 3 { + return "", fmt.Errorf("invalid ciphertext format: expected iv:authTag:ciphertext") + } + + nonce, err := base64.StdEncoding.DecodeString(parts[0]) + if err != nil { + return "", fmt.Errorf("decode IV: %w", err) + } + + authTag, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return "", fmt.Errorf("decode auth tag: %w", err) + } + + encrypted, err := base64.StdEncoding.DecodeString(parts[2]) + if err != nil { + return "", fmt.Errorf("decode ciphertext: %w", err) + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", fmt.Errorf("create AES cipher: %w", err) + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("create GCM: %w", err) + } + + // Reconstruct the sealed data: ciphertext + authTag + sealed := append(encrypted, authTag...) + + plaintext, err := gcm.Open(nil, nonce, sealed, nil) + if err != nil { + return "", fmt.Errorf("decrypt: %w", err) + } + + return string(plaintext), nil +} diff --git a/backend/internal/payment/crypto_test.go b/backend/internal/payment/crypto_test.go new file mode 100644 index 0000000000000000000000000000000000000000..da8b6006e1e9b648d3466ff93454a67e6aaa869b --- /dev/null +++ b/backend/internal/payment/crypto_test.go @@ -0,0 +1,183 @@ +package payment + +import ( + "crypto/rand" + "strings" + "testing" +) + +func makeKey(t *testing.T) []byte { + t.Helper() + key := make([]byte, 32) + if _, err := rand.Read(key); err != nil { + t.Fatalf("generate random key: %v", err) + } + return key +} + +func TestEncryptDecryptRoundTrip(t *testing.T) { + t.Parallel() + key := makeKey(t) + + plaintexts := []string{ + "hello world", + "short", + "a longer string with special chars: !@#$%^&*()", + `{"key":"value","num":42}`, + "你好世界 unicode test 🎉", + strings.Repeat("x", 10000), + } + + for _, pt := range plaintexts { + encrypted, err := Encrypt(pt, key) + if err != nil { + t.Fatalf("Encrypt(%q) error: %v", pt[:min(len(pt), 30)], err) + } + decrypted, err := Decrypt(encrypted, key) + if err != nil { + t.Fatalf("Decrypt error for plaintext %q: %v", pt[:min(len(pt), 30)], err) + } + if decrypted != pt { + t.Fatalf("round-trip failed: got %q, want %q", decrypted[:min(len(decrypted), 30)], pt[:min(len(pt), 30)]) + } + } +} + +func TestEncryptProducesDifferentCiphertexts(t *testing.T) { + t.Parallel() + key := makeKey(t) + + ct1, err := Encrypt("same plaintext", key) + if err != nil { + t.Fatalf("first Encrypt error: %v", err) + } + ct2, err := Encrypt("same plaintext", key) + if err != nil { + t.Fatalf("second Encrypt error: %v", err) + } + if ct1 == ct2 { + t.Fatal("two encryptions of the same plaintext should produce different ciphertexts (random nonce)") + } +} + +func TestDecryptWithWrongKeyFails(t *testing.T) { + t.Parallel() + key1 := makeKey(t) + key2 := makeKey(t) + + encrypted, err := Encrypt("secret data", key1) + if err != nil { + t.Fatalf("Encrypt error: %v", err) + } + + _, err = Decrypt(encrypted, key2) + if err == nil { + t.Fatal("Decrypt with wrong key should fail, but got nil error") + } +} + +func TestEncryptRejectsInvalidKeyLength(t *testing.T) { + t.Parallel() + badKeys := [][]byte{ + nil, + make([]byte, 0), + make([]byte, 16), + make([]byte, 31), + make([]byte, 33), + make([]byte, 64), + } + for _, key := range badKeys { + _, err := Encrypt("test", key) + if err == nil { + t.Fatalf("Encrypt should reject key of length %d", len(key)) + } + } +} + +func TestDecryptRejectsInvalidKeyLength(t *testing.T) { + t.Parallel() + badKeys := [][]byte{ + nil, + make([]byte, 16), + make([]byte, 33), + } + for _, key := range badKeys { + _, err := Decrypt("dummydata:dummydata:dummydata", key) + if err == nil { + t.Fatalf("Decrypt should reject key of length %d", len(key)) + } + } +} + +func TestEncryptEmptyPlaintext(t *testing.T) { + t.Parallel() + key := makeKey(t) + + encrypted, err := Encrypt("", key) + if err != nil { + t.Fatalf("Encrypt empty plaintext error: %v", err) + } + decrypted, err := Decrypt(encrypted, key) + if err != nil { + t.Fatalf("Decrypt empty plaintext error: %v", err) + } + if decrypted != "" { + t.Fatalf("expected empty string, got %q", decrypted) + } +} + +func TestEncryptDecryptUnicodeJSON(t *testing.T) { + t.Parallel() + key := makeKey(t) + + jsonContent := `{"name":"测试用户","email":"test@example.com","balance":100.50}` + encrypted, err := Encrypt(jsonContent, key) + if err != nil { + t.Fatalf("Encrypt JSON error: %v", err) + } + decrypted, err := Decrypt(encrypted, key) + if err != nil { + t.Fatalf("Decrypt JSON error: %v", err) + } + if decrypted != jsonContent { + t.Fatalf("JSON round-trip failed: got %q, want %q", decrypted, jsonContent) + } +} + +func TestDecryptInvalidFormat(t *testing.T) { + t.Parallel() + key := makeKey(t) + + invalidInputs := []string{ + "", + "nodelimiter", + "only:two", + "invalid:base64:!!!", + } + for _, input := range invalidInputs { + _, err := Decrypt(input, key) + if err == nil { + t.Fatalf("Decrypt(%q) should fail but got nil error", input) + } + } +} + +func TestCiphertextFormat(t *testing.T) { + t.Parallel() + key := makeKey(t) + + encrypted, err := Encrypt("test", key) + if err != nil { + t.Fatalf("Encrypt error: %v", err) + } + + parts := strings.SplitN(encrypted, ":", 3) + if len(parts) != 3 { + t.Fatalf("ciphertext should have format iv:authTag:ciphertext, got %d parts", len(parts)) + } + for i, part := range parts { + if part == "" { + t.Fatalf("ciphertext part %d is empty", i) + } + } +} diff --git a/backend/internal/payment/fee.go b/backend/internal/payment/fee.go new file mode 100644 index 0000000000000000000000000000000000000000..e2128e5efa33e2f2de18b7f67c209d94b6f60a9e --- /dev/null +++ b/backend/internal/payment/fee.go @@ -0,0 +1,19 @@ +package payment + +import ( + "github.com/shopspring/decimal" +) + +// CalculatePayAmount computes the total pay amount given a recharge amount and +// fee rate (percentage). Fee = amount * feeRate / 100, rounded UP (away from zero) +// to 2 decimal places. The returned string is formatted to exactly 2 decimal places. +// If feeRate <= 0, the amount is returned as-is (formatted to 2 decimal places). +func CalculatePayAmount(rechargeAmount float64, feeRate float64) string { + amount := decimal.NewFromFloat(rechargeAmount) + if feeRate <= 0 { + return amount.StringFixed(2) + } + rate := decimal.NewFromFloat(feeRate) + fee := amount.Mul(rate).Div(decimal.NewFromInt(100)).RoundUp(2) + return amount.Add(fee).StringFixed(2) +} diff --git a/backend/internal/payment/fee_test.go b/backend/internal/payment/fee_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c58d1082a91f1f0f06bd100c7fc89c6edb5d1760 --- /dev/null +++ b/backend/internal/payment/fee_test.go @@ -0,0 +1,111 @@ +package payment + +import ( + "testing" +) + +func TestCalculatePayAmount(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + amount float64 + feeRate float64 + expected string + }{ + { + name: "zero fee rate returns same amount", + amount: 100.00, + feeRate: 0, + expected: "100.00", + }, + { + name: "negative fee rate returns same amount", + amount: 50.00, + feeRate: -5, + expected: "50.00", + }, + { + name: "1 percent fee rate", + amount: 100.00, + feeRate: 1, + expected: "101.00", + }, + { + name: "5 percent fee on 200", + amount: 200.00, + feeRate: 5, + expected: "210.00", + }, + { + name: "fee rounds UP to 2 decimal places", + amount: 100.00, + feeRate: 3, + expected: "103.00", + }, + { + name: "fee rounds UP small remainder", + amount: 10.00, + feeRate: 3.33, + expected: "10.34", // 10 * 3.33 / 100 = 0.333 -> round up -> 0.34 + }, + { + name: "very small amount", + amount: 0.01, + feeRate: 1, + expected: "0.02", // 0.01 * 1/100 = 0.0001 -> round up -> 0.01 -> total 0.02 + }, + { + name: "large amount", + amount: 99999.99, + feeRate: 10, + expected: "109999.99", // 99999.99 * 10/100 = 9999.999 -> round up -> 10000.00 -> total 109999.99 + }, + { + name: "100 percent fee rate doubles amount", + amount: 50.00, + feeRate: 100, + expected: "100.00", + }, + { + name: "precision 0.01 fee difference", + amount: 100.00, + feeRate: 1.01, + expected: "101.01", // 100 * 1.01/100 = 1.01 + }, + { + name: "precision 0.02 fee", + amount: 100.00, + feeRate: 1.02, + expected: "101.02", + }, + { + name: "zero amount with positive fee", + amount: 0, + feeRate: 5, + expected: "0.00", + }, + { + name: "fractional amount no fee", + amount: 19.99, + feeRate: 0, + expected: "19.99", + }, + { + name: "fractional fee that causes rounding up", + amount: 33.33, + feeRate: 7.77, + expected: "35.92", // 33.33 * 7.77 / 100 = 2.589741 -> round up -> 2.59 -> total 35.92 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := CalculatePayAmount(tt.amount, tt.feeRate) + if got != tt.expected { + t.Fatalf("CalculatePayAmount(%v, %v) = %q, want %q", tt.amount, tt.feeRate, got, tt.expected) + } + }) + } +} diff --git a/backend/internal/payment/load_balancer.go b/backend/internal/payment/load_balancer.go new file mode 100644 index 0000000000000000000000000000000000000000..afe607e0cfad50ba009050a1c073dc9520467d0d --- /dev/null +++ b/backend/internal/payment/load_balancer.go @@ -0,0 +1,328 @@ +package payment + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "strings" + "sync/atomic" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" +) + +// Strategy represents a load balancing strategy for provider instance selection. +type Strategy string + +const ( + StrategyRoundRobin Strategy = "round-robin" + StrategyLeastAmount Strategy = "least-amount" +) + +// ChannelLimits holds limits for a single payment channel within a provider instance. +type ChannelLimits struct { + DailyLimit float64 `json:"dailyLimit,omitempty"` + SingleMin float64 `json:"singleMin,omitempty"` + SingleMax float64 `json:"singleMax,omitempty"` +} + +// InstanceLimits holds per-channel limits for a provider instance (JSON). +type InstanceLimits map[string]ChannelLimits + +// LoadBalancer selects a provider instance for a given payment type. +type LoadBalancer interface { + GetInstanceConfig(ctx context.Context, instanceID int64) (map[string]string, error) + SelectInstance(ctx context.Context, providerKey string, paymentType PaymentType, strategy Strategy, orderAmount float64) (*InstanceSelection, error) +} + +// DefaultLoadBalancer implements LoadBalancer using database queries. +type DefaultLoadBalancer struct { + db *dbent.Client + encryptionKey []byte + counter atomic.Uint64 +} + +// NewDefaultLoadBalancer creates a new load balancer. +func NewDefaultLoadBalancer(db *dbent.Client, encryptionKey []byte) *DefaultLoadBalancer { + return &DefaultLoadBalancer{db: db, encryptionKey: encryptionKey} +} + +// instanceCandidate pairs an instance with its pre-fetched daily usage. +type instanceCandidate struct { + inst *dbent.PaymentProviderInstance + dailyUsed float64 // includes PENDING orders +} + +// SelectInstance picks an enabled instance for the given provider key and payment type. +// +// Flow: +// 1. Query all enabled instances for providerKey, filter by supported paymentType +// 2. Batch-query daily usage (PENDING + PAID + COMPLETED + RECHARGING) for all candidates +// 3. Filter out instances where: single-min/max violated OR daily remaining < orderAmount +// 4. Pick from survivors using the configured strategy (round-robin / least-amount) +// 5. If all filtered out, fall back to full list (let the provider itself reject) +func (lb *DefaultLoadBalancer) SelectInstance( + ctx context.Context, + providerKey string, + paymentType PaymentType, + strategy Strategy, + orderAmount float64, +) (*InstanceSelection, error) { + // Step 1: query enabled instances matching payment type. + instances, err := lb.queryEnabledInstances(ctx, providerKey, paymentType) + if err != nil { + return nil, err + } + + // Step 2: batch-fetch daily usage for all candidates. + candidates := lb.attachDailyUsage(ctx, instances) + + // Step 3: filter by limits. + available := filterByLimits(candidates, paymentType, orderAmount) + if len(available) == 0 { + slog.Warn("all instances exceeded limits, using full candidate list", + "provider", providerKey, "payment_type", paymentType, + "order_amount", orderAmount, "count", len(candidates)) + available = candidates + } + + // Step 4: pick by strategy. + selected := lb.pickByStrategy(available, strategy) + return lb.buildSelection(selected.inst) +} + +// queryEnabledInstances returns enabled instances for providerKey that support paymentType. +func (lb *DefaultLoadBalancer) queryEnabledInstances( + ctx context.Context, + providerKey string, + paymentType PaymentType, +) ([]*dbent.PaymentProviderInstance, error) { + instances, err := lb.db.PaymentProviderInstance.Query(). + Where( + paymentproviderinstance.ProviderKey(providerKey), + paymentproviderinstance.Enabled(true), + ). + Order(dbent.Asc(paymentproviderinstance.FieldSortOrder)). + All(ctx) + if err != nil { + return nil, fmt.Errorf("query provider instances: %w", err) + } + + var matched []*dbent.PaymentProviderInstance + for _, inst := range instances { + if paymentType == providerKey || InstanceSupportsType(inst.SupportedTypes, paymentType) { + matched = append(matched, inst) + } + } + if len(matched) == 0 { + return nil, fmt.Errorf("no enabled instance for provider %s type %s", providerKey, paymentType) + } + return matched, nil +} + +// attachDailyUsage queries daily usage for each instance in a single pass. +// Usage includes PENDING orders to avoid over-committing capacity. +func (lb *DefaultLoadBalancer) attachDailyUsage( + ctx context.Context, + instances []*dbent.PaymentProviderInstance, +) []instanceCandidate { + todayStart := startOfDay(time.Now()) + + // Collect instance IDs. + ids := make([]string, len(instances)) + for i, inst := range instances { + ids[i] = fmt.Sprintf("%d", inst.ID) + } + + // Batch query: sum pay_amount grouped by provider_instance_id. + type row struct { + InstanceID string `json:"provider_instance_id"` + Sum float64 `json:"sum"` + } + var rows []row + err := lb.db.PaymentOrder.Query(). + Where( + paymentorder.ProviderInstanceIDIn(ids...), + paymentorder.StatusIn( + OrderStatusPending, OrderStatusPaid, + OrderStatusCompleted, OrderStatusRecharging, + ), + paymentorder.CreatedAtGTE(todayStart), + ). + GroupBy(paymentorder.FieldProviderInstanceID). + Aggregate(dbent.Sum(paymentorder.FieldPayAmount)). + Scan(ctx, &rows) + if err != nil { + slog.Warn("batch daily usage query failed, treating all as zero", "error", err) + } + + usageMap := make(map[string]float64, len(rows)) + for _, r := range rows { + usageMap[r.InstanceID] = r.Sum + } + + candidates := make([]instanceCandidate, len(instances)) + for i, inst := range instances { + candidates[i] = instanceCandidate{ + inst: inst, + dailyUsed: usageMap[fmt.Sprintf("%d", inst.ID)], + } + } + return candidates +} + +// filterByLimits removes instances that cannot accommodate the order: +// - orderAmount outside single-transaction [min, max] +// - daily remaining capacity (limit - used) < orderAmount +func filterByLimits(candidates []instanceCandidate, paymentType PaymentType, orderAmount float64) []instanceCandidate { + var result []instanceCandidate + for _, c := range candidates { + cl := getInstanceChannelLimits(c.inst, paymentType) + + if cl.SingleMin > 0 && orderAmount < cl.SingleMin { + slog.Info("order below instance single min, skipping", + "instance_id", c.inst.ID, "order", orderAmount, "min", cl.SingleMin) + continue + } + if cl.SingleMax > 0 && orderAmount > cl.SingleMax { + slog.Info("order above instance single max, skipping", + "instance_id", c.inst.ID, "order", orderAmount, "max", cl.SingleMax) + continue + } + if cl.DailyLimit > 0 && c.dailyUsed+orderAmount > cl.DailyLimit { + slog.Info("instance daily remaining insufficient, skipping", + "instance_id", c.inst.ID, "used", c.dailyUsed, + "order", orderAmount, "limit", cl.DailyLimit) + continue + } + + result = append(result, c) + } + return result +} + +// getInstanceChannelLimits returns the channel limits for a specific payment type. +func getInstanceChannelLimits(inst *dbent.PaymentProviderInstance, paymentType PaymentType) ChannelLimits { + if inst.Limits == "" { + return ChannelLimits{} + } + var limits InstanceLimits + if err := json.Unmarshal([]byte(inst.Limits), &limits); err != nil { + return ChannelLimits{} + } + // For Stripe, limits are stored under the provider key "stripe". + lookupKey := paymentType + if inst.ProviderKey == "stripe" { + lookupKey = "stripe" + } + if cl, ok := limits[lookupKey]; ok { + return cl + } + return ChannelLimits{} +} + +// pickByStrategy selects one instance from the available candidates. +func (lb *DefaultLoadBalancer) pickByStrategy(candidates []instanceCandidate, strategy Strategy) instanceCandidate { + if strategy == StrategyLeastAmount && len(candidates) > 1 { + return pickLeastAmount(candidates) + } + // Default: round-robin. + idx := lb.counter.Add(1) % uint64(len(candidates)) + return candidates[idx] +} + +// pickLeastAmount selects the instance with the lowest daily usage. +// No extra DB queries — usage was pre-fetched in attachDailyUsage. +func pickLeastAmount(candidates []instanceCandidate) instanceCandidate { + best := candidates[0] + for _, c := range candidates[1:] { + if c.dailyUsed < best.dailyUsed { + best = c + } + } + return best +} + +func (lb *DefaultLoadBalancer) buildSelection(selected *dbent.PaymentProviderInstance) (*InstanceSelection, error) { + config, err := lb.decryptConfig(selected.Config) + if err != nil { + return nil, fmt.Errorf("decrypt instance %d config: %w", selected.ID, err) + } + + if selected.PaymentMode != "" { + config["paymentMode"] = selected.PaymentMode + } + + return &InstanceSelection{ + InstanceID: fmt.Sprintf("%d", selected.ID), + Config: config, + SupportedTypes: selected.SupportedTypes, + PaymentMode: selected.PaymentMode, + }, nil +} + +func (lb *DefaultLoadBalancer) decryptConfig(encrypted string) (map[string]string, error) { + plaintext, err := Decrypt(encrypted, lb.encryptionKey) + if err != nil { + return nil, err + } + var config map[string]string + if err := json.Unmarshal([]byte(plaintext), &config); err != nil { + return nil, fmt.Errorf("unmarshal config: %w", err) + } + return config, nil +} + +// GetInstanceDailyAmount returns the total completed order amount for an instance today. +func (lb *DefaultLoadBalancer) GetInstanceDailyAmount(ctx context.Context, instanceID string) (float64, error) { + todayStart := startOfDay(time.Now()) + + var result []struct { + Sum float64 `json:"sum"` + } + err := lb.db.PaymentOrder.Query(). + Where( + paymentorder.ProviderInstanceID(instanceID), + paymentorder.StatusIn(OrderStatusCompleted, OrderStatusPaid, OrderStatusRecharging), + paymentorder.PaidAtGTE(todayStart), + ). + Aggregate(dbent.Sum(paymentorder.FieldPayAmount)). + Scan(ctx, &result) + if err != nil { + return 0, fmt.Errorf("query daily amount: %w", err) + } + if len(result) > 0 { + return result[0].Sum, nil + } + return 0, nil +} + +func startOfDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) +} + +// InstanceSupportsType checks if the given supported types string includes the target type. +// An empty supportedTypes string means all types are supported. +func InstanceSupportsType(supportedTypes string, target PaymentType) bool { + if supportedTypes == "" { + return true + } + for _, t := range strings.Split(supportedTypes, ",") { + if strings.TrimSpace(t) == target { + return true + } + } + return false +} + +// GetInstanceConfig decrypts and returns the configuration for a provider instance by ID. +func (lb *DefaultLoadBalancer) GetInstanceConfig(ctx context.Context, instanceID int64) (map[string]string, error) { + inst, err := lb.db.PaymentProviderInstance.Get(ctx, instanceID) + if err != nil { + return nil, fmt.Errorf("get instance %d: %w", instanceID, err) + } + return lb.decryptConfig(inst.Config) +} diff --git a/backend/internal/payment/load_balancer_test.go b/backend/internal/payment/load_balancer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..568b56a30debac86ebac9e019dd81cdfffd16a1d --- /dev/null +++ b/backend/internal/payment/load_balancer_test.go @@ -0,0 +1,474 @@ +//go:build unit + +package payment + +import ( + "encoding/json" + "testing" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" +) + +func TestInstanceSupportsType(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + supportedTypes string + target PaymentType + expected bool + }{ + { + name: "exact match single type", + supportedTypes: "alipay", + target: "alipay", + expected: true, + }, + { + name: "no match single type", + supportedTypes: "wxpay", + target: "alipay", + expected: false, + }, + { + name: "match in comma-separated list", + supportedTypes: "alipay,wxpay,stripe", + target: "wxpay", + expected: true, + }, + { + name: "first in comma-separated list", + supportedTypes: "alipay,wxpay", + target: "alipay", + expected: true, + }, + { + name: "last in comma-separated list", + supportedTypes: "alipay,wxpay,stripe", + target: "stripe", + expected: true, + }, + { + name: "no match in comma-separated list", + supportedTypes: "alipay,wxpay", + target: "stripe", + expected: false, + }, + { + name: "empty target", + supportedTypes: "alipay,wxpay", + target: "", + expected: false, + }, + { + name: "types with spaces are trimmed", + supportedTypes: " alipay , wxpay ", + target: "alipay", + expected: true, + }, + { + name: "partial match should not succeed", + supportedTypes: "alipay_direct", + target: "alipay", + expected: false, + }, + { + name: "empty supported types means all supported", + supportedTypes: "", + target: "alipay", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := InstanceSupportsType(tt.supportedTypes, tt.target) + if got != tt.expected { + t.Fatalf("InstanceSupportsType(%q, %q) = %v, want %v", tt.supportedTypes, tt.target, got, tt.expected) + } + }) + } +} + +// --------------------------------------------------------------------------- +// Helper to build test PaymentProviderInstance values +// --------------------------------------------------------------------------- + +func testInstance(id int64, providerKey, limits string) *dbent.PaymentProviderInstance { + return &dbent.PaymentProviderInstance{ + ID: id, + ProviderKey: providerKey, + Limits: limits, + Enabled: true, + } +} + +// makeLimitsJSON builds a limits JSON string for a single payment type. +func makeLimitsJSON(paymentType string, cl ChannelLimits) string { + m := map[string]ChannelLimits{paymentType: cl} + b, _ := json.Marshal(m) + return string(b) +} + +// --------------------------------------------------------------------------- +// filterByLimits +// --------------------------------------------------------------------------- + +func TestFilterByLimits(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + candidates []instanceCandidate + paymentType PaymentType + orderAmount float64 + wantIDs []int64 // expected surviving instance IDs + }{ + { + name: "order below SingleMin is filtered out", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10})), dailyUsed: 0}, + }, + paymentType: "alipay", + orderAmount: 5, + wantIDs: nil, + }, + { + name: "order at exact SingleMin boundary passes", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10})), dailyUsed: 0}, + }, + paymentType: "alipay", + orderAmount: 10, + wantIDs: []int64{1}, + }, + { + name: "order above SingleMax is filtered out", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMax: 100})), dailyUsed: 0}, + }, + paymentType: "alipay", + orderAmount: 150, + wantIDs: nil, + }, + { + name: "order at exact SingleMax boundary passes", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMax: 100})), dailyUsed: 0}, + }, + paymentType: "alipay", + orderAmount: 100, + wantIDs: []int64{1}, + }, + { + name: "daily used + orderAmount exceeding dailyLimit is filtered out", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 500})), dailyUsed: 480}, + }, + paymentType: "alipay", + orderAmount: 30, + wantIDs: nil, // 480+30=510 > 500 + }, + { + name: "daily used + orderAmount equal to dailyLimit passes (strict greater-than)", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 500})), dailyUsed: 480}, + }, + paymentType: "alipay", + orderAmount: 20, + wantIDs: []int64{1}, // 480+20=500, 500 > 500 is false → passes + }, + { + name: "daily used + orderAmount below dailyLimit passes", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 500})), dailyUsed: 400}, + }, + paymentType: "alipay", + orderAmount: 50, + wantIDs: []int64{1}, + }, + { + name: "no limits configured passes through", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", ""), dailyUsed: 99999}, + }, + paymentType: "alipay", + orderAmount: 100, + wantIDs: []int64{1}, + }, + { + name: "multiple candidates with partial filtering", + candidates: []instanceCandidate{ + // singleMax=50, order=80 → filtered out + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMax: 50})), dailyUsed: 0}, + // no limits → passes + {inst: testInstance(2, "easypay", ""), dailyUsed: 0}, + // singleMin=100, order=80 → filtered out + {inst: testInstance(3, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 100})), dailyUsed: 0}, + // daily limit ok → passes (500+80=580 < 1000) + {inst: testInstance(4, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 1000})), dailyUsed: 500}, + }, + paymentType: "alipay", + orderAmount: 80, + wantIDs: []int64{2, 4}, + }, + { + name: "zero SingleMin and SingleMax means no single-transaction limit", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 0, SingleMax: 0, DailyLimit: 0})), dailyUsed: 0}, + }, + paymentType: "alipay", + orderAmount: 99999, + wantIDs: []int64{1}, + }, + { + name: "all limits combined - order passes all checks", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10, SingleMax: 200, DailyLimit: 1000})), dailyUsed: 500}, + }, + paymentType: "alipay", + orderAmount: 50, + wantIDs: []int64{1}, + }, + { + name: "all limits combined - order fails SingleMin", + candidates: []instanceCandidate{ + {inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10, SingleMax: 200, DailyLimit: 1000})), dailyUsed: 500}, + }, + paymentType: "alipay", + orderAmount: 5, + wantIDs: nil, + }, + { + name: "empty candidates returns empty", + candidates: nil, + paymentType: "alipay", + orderAmount: 10, + wantIDs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := filterByLimits(tt.candidates, tt.paymentType, tt.orderAmount) + gotIDs := make([]int64, len(got)) + for i, c := range got { + gotIDs[i] = c.inst.ID + } + if !int64SliceEqual(gotIDs, tt.wantIDs) { + t.Fatalf("filterByLimits() returned IDs %v, want %v", gotIDs, tt.wantIDs) + } + }) + } +} + +// --------------------------------------------------------------------------- +// pickLeastAmount +// --------------------------------------------------------------------------- + +func TestPickLeastAmount(t *testing.T) { + t.Parallel() + + t.Run("picks candidate with lowest dailyUsed", func(t *testing.T) { + t.Parallel() + candidates := []instanceCandidate{ + {inst: testInstance(1, "easypay", ""), dailyUsed: 300}, + {inst: testInstance(2, "easypay", ""), dailyUsed: 100}, + {inst: testInstance(3, "easypay", ""), dailyUsed: 200}, + } + got := pickLeastAmount(candidates) + if got.inst.ID != 2 { + t.Fatalf("pickLeastAmount() picked instance %d, want 2", got.inst.ID) + } + }) + + t.Run("with equal dailyUsed picks the first one", func(t *testing.T) { + t.Parallel() + candidates := []instanceCandidate{ + {inst: testInstance(1, "easypay", ""), dailyUsed: 100}, + {inst: testInstance(2, "easypay", ""), dailyUsed: 100}, + {inst: testInstance(3, "easypay", ""), dailyUsed: 200}, + } + got := pickLeastAmount(candidates) + if got.inst.ID != 1 { + t.Fatalf("pickLeastAmount() picked instance %d, want 1 (first with lowest)", got.inst.ID) + } + }) + + t.Run("single candidate returns that candidate", func(t *testing.T) { + t.Parallel() + candidates := []instanceCandidate{ + {inst: testInstance(42, "easypay", ""), dailyUsed: 999}, + } + got := pickLeastAmount(candidates) + if got.inst.ID != 42 { + t.Fatalf("pickLeastAmount() picked instance %d, want 42", got.inst.ID) + } + }) + + t.Run("zero usage among non-zero picks zero", func(t *testing.T) { + t.Parallel() + candidates := []instanceCandidate{ + {inst: testInstance(1, "easypay", ""), dailyUsed: 500}, + {inst: testInstance(2, "easypay", ""), dailyUsed: 0}, + {inst: testInstance(3, "easypay", ""), dailyUsed: 300}, + } + got := pickLeastAmount(candidates) + if got.inst.ID != 2 { + t.Fatalf("pickLeastAmount() picked instance %d, want 2", got.inst.ID) + } + }) +} + +// --------------------------------------------------------------------------- +// getInstanceChannelLimits +// --------------------------------------------------------------------------- + +func TestGetInstanceChannelLimits(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + inst *dbent.PaymentProviderInstance + paymentType PaymentType + want ChannelLimits + }{ + { + name: "empty limits string returns zero ChannelLimits", + inst: testInstance(1, "easypay", ""), + paymentType: "alipay", + want: ChannelLimits{}, + }, + { + name: "invalid JSON returns zero ChannelLimits", + inst: testInstance(1, "easypay", "not-json{"), + paymentType: "alipay", + want: ChannelLimits{}, + }, + { + name: "valid JSON with matching payment type", + inst: testInstance(1, "easypay", + `{"alipay":{"singleMin":5,"singleMax":200,"dailyLimit":1000}}`), + paymentType: "alipay", + want: ChannelLimits{SingleMin: 5, SingleMax: 200, DailyLimit: 1000}, + }, + { + name: "payment type not in limits returns zero ChannelLimits", + inst: testInstance(1, "easypay", + `{"alipay":{"singleMin":5,"singleMax":200}}`), + paymentType: "wxpay", + want: ChannelLimits{}, + }, + { + name: "stripe provider uses stripe lookup key regardless of payment type", + inst: testInstance(1, "stripe", + `{"stripe":{"singleMin":10,"singleMax":500,"dailyLimit":5000}}`), + paymentType: "alipay", + want: ChannelLimits{SingleMin: 10, SingleMax: 500, DailyLimit: 5000}, + }, + { + name: "stripe provider ignores payment type key even if present", + inst: testInstance(1, "stripe", + `{"stripe":{"singleMin":10,"singleMax":500},"alipay":{"singleMin":1,"singleMax":100}}`), + paymentType: "alipay", + want: ChannelLimits{SingleMin: 10, SingleMax: 500}, + }, + { + name: "non-stripe provider uses payment type as lookup key", + inst: testInstance(1, "easypay", + `{"alipay":{"singleMin":5},"wxpay":{"singleMin":10}}`), + paymentType: "wxpay", + want: ChannelLimits{SingleMin: 10}, + }, + { + name: "valid JSON with partial limits (only dailyLimit)", + inst: testInstance(1, "easypay", + `{"alipay":{"dailyLimit":800}}`), + paymentType: "alipay", + want: ChannelLimits{DailyLimit: 800}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := getInstanceChannelLimits(tt.inst, tt.paymentType) + if got != tt.want { + t.Fatalf("getInstanceChannelLimits() = %+v, want %+v", got, tt.want) + } + }) + } +} + +// --------------------------------------------------------------------------- +// startOfDay +// --------------------------------------------------------------------------- + +func TestStartOfDay(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in time.Time + want time.Time + }{ + { + name: "midday returns midnight of same day", + in: time.Date(2025, 6, 15, 14, 30, 45, 123456789, time.UTC), + want: time.Date(2025, 6, 15, 0, 0, 0, 0, time.UTC), + }, + { + name: "midnight returns same time", + in: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), + want: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "last second of day returns midnight of same day", + in: time.Date(2025, 12, 31, 23, 59, 59, 999999999, time.UTC), + want: time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC), + }, + { + name: "preserves timezone location", + in: time.Date(2025, 3, 10, 15, 0, 0, 0, time.FixedZone("CST", 8*3600)), + want: time.Date(2025, 3, 10, 0, 0, 0, 0, time.FixedZone("CST", 8*3600)), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := startOfDay(tt.in) + if !got.Equal(tt.want) { + t.Fatalf("startOfDay(%v) = %v, want %v", tt.in, got, tt.want) + } + // Also verify location is preserved. + if got.Location().String() != tt.want.Location().String() { + t.Fatalf("startOfDay() location = %v, want %v", got.Location(), tt.want.Location()) + } + }) + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +// int64SliceEqual compares two int64 slices for equality. +// Both nil and empty slices are treated as equal. +func int64SliceEqual(a, b []int64) bool { + if len(a) == 0 && len(b) == 0 { + return true + } + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/backend/internal/payment/provider/alipay.go b/backend/internal/payment/provider/alipay.go new file mode 100644 index 0000000000000000000000000000000000000000..3eca0b2cc2cac8e7ba6d0d3197de38f82c7c5ede --- /dev/null +++ b/backend/internal/payment/provider/alipay.go @@ -0,0 +1,279 @@ +package provider + +import ( + "context" + "fmt" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/smartwalle/alipay/v3" +) + +// Alipay product codes. +const ( + alipayProductCodePagePay = "FAST_INSTANT_TRADE_PAY" + alipayProductCodeWapPay = "QUICK_WAP_WAY" +) + +// Alipay response constants. +const ( + alipayFundChangeYes = "Y" + alipayErrTradeNotExist = "ACQ.TRADE_NOT_EXIST" + alipayRefundSuffix = "-refund" +) + +// Alipay implements payment.Provider and payment.CancelableProvider using the smartwalle/alipay SDK. +type Alipay struct { + instanceID string + config map[string]string // appId, privateKey, publicKey (or alipayPublicKey), notifyUrl, returnUrl + + mu sync.Mutex + client *alipay.Client +} + +// NewAlipay creates a new Alipay provider instance. +func NewAlipay(instanceID string, config map[string]string) (*Alipay, error) { + required := []string{"appId", "privateKey"} + for _, k := range required { + if config[k] == "" { + return nil, fmt.Errorf("alipay config missing required key: %s", k) + } + } + return &Alipay{ + instanceID: instanceID, + config: config, + }, nil +} + +func (a *Alipay) getClient() (*alipay.Client, error) { + a.mu.Lock() + defer a.mu.Unlock() + if a.client != nil { + return a.client, nil + } + client, err := alipay.New(a.config["appId"], a.config["privateKey"], true) + if err != nil { + return nil, fmt.Errorf("alipay init client: %w", err) + } + pubKey := a.config["publicKey"] + if pubKey == "" { + pubKey = a.config["alipayPublicKey"] + } + if pubKey == "" { + return nil, fmt.Errorf("alipay config missing required key: publicKey (or alipayPublicKey)") + } + if err := client.LoadAliPayPublicKey(pubKey); err != nil { + return nil, fmt.Errorf("alipay load public key: %w", err) + } + a.client = client + return a.client, nil +} + +func (a *Alipay) Name() string { return "Alipay" } +func (a *Alipay) ProviderKey() string { return payment.TypeAlipay } +func (a *Alipay) SupportedTypes() []payment.PaymentType { + return []payment.PaymentType{payment.TypeAlipayDirect} +} + +// CreatePayment creates an Alipay payment page URL. +func (a *Alipay) CreatePayment(_ context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) { + client, err := a.getClient() + if err != nil { + return nil, err + } + + notifyURL := a.config["notifyUrl"] + if req.NotifyURL != "" { + notifyURL = req.NotifyURL + } + returnURL := a.config["returnUrl"] + if req.ReturnURL != "" { + returnURL = req.ReturnURL + } + + if req.IsMobile { + return a.createTrade(client, req, notifyURL, returnURL, true) + } + return a.createTrade(client, req, notifyURL, returnURL, false) +} + +func (a *Alipay) createTrade(client *alipay.Client, req payment.CreatePaymentRequest, notifyURL, returnURL string, isMobile bool) (*payment.CreatePaymentResponse, error) { + if isMobile { + param := alipay.TradeWapPay{} + param.OutTradeNo = req.OrderID + param.TotalAmount = req.Amount + param.Subject = req.Subject + param.ProductCode = alipayProductCodeWapPay + param.NotifyURL = notifyURL + param.ReturnURL = returnURL + + payURL, err := client.TradeWapPay(param) + if err != nil { + return nil, fmt.Errorf("alipay TradeWapPay: %w", err) + } + return &payment.CreatePaymentResponse{ + TradeNo: req.OrderID, + PayURL: payURL.String(), + }, nil + } + + param := alipay.TradePagePay{} + param.OutTradeNo = req.OrderID + param.TotalAmount = req.Amount + param.Subject = req.Subject + param.ProductCode = alipayProductCodePagePay + param.NotifyURL = notifyURL + param.ReturnURL = returnURL + + payURL, err := client.TradePagePay(param) + if err != nil { + return nil, fmt.Errorf("alipay TradePagePay: %w", err) + } + return &payment.CreatePaymentResponse{ + TradeNo: req.OrderID, + PayURL: payURL.String(), + QRCode: payURL.String(), + }, nil +} + +// QueryOrder queries the trade status via Alipay. +func (a *Alipay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) { + client, err := a.getClient() + if err != nil { + return nil, err + } + + result, err := client.TradeQuery(ctx, alipay.TradeQuery{OutTradeNo: tradeNo}) + if err != nil { + if isTradeNotExist(err) { + return &payment.QueryOrderResponse{ + TradeNo: tradeNo, + Status: payment.ProviderStatusPending, + }, nil + } + return nil, fmt.Errorf("alipay TradeQuery: %w", err) + } + + status := payment.ProviderStatusPending + switch result.TradeStatus { + case alipay.TradeStatusSuccess, alipay.TradeStatusFinished: + status = payment.ProviderStatusPaid + case alipay.TradeStatusClosed: + status = payment.ProviderStatusFailed + } + + amount, err := strconv.ParseFloat(result.TotalAmount, 64) + if err != nil { + return nil, fmt.Errorf("alipay parse amount %q: %w", result.TotalAmount, err) + } + + return &payment.QueryOrderResponse{ + TradeNo: result.TradeNo, + Status: status, + Amount: amount, + PaidAt: result.SendPayDate, + }, nil +} + +// VerifyNotification decodes and verifies an Alipay async notification. +func (a *Alipay) VerifyNotification(ctx context.Context, rawBody string, _ map[string]string) (*payment.PaymentNotification, error) { + client, err := a.getClient() + if err != nil { + return nil, err + } + + values, err := url.ParseQuery(rawBody) + if err != nil { + return nil, fmt.Errorf("alipay parse notification: %w", err) + } + + notification, err := client.DecodeNotification(ctx, values) + if err != nil { + return nil, fmt.Errorf("alipay verify notification: %w", err) + } + + status := payment.ProviderStatusFailed + if notification.TradeStatus == alipay.TradeStatusSuccess || notification.TradeStatus == alipay.TradeStatusFinished { + status = payment.ProviderStatusSuccess + } + + amount, err := strconv.ParseFloat(notification.TotalAmount, 64) + if err != nil { + return nil, fmt.Errorf("alipay parse notification amount %q: %w", notification.TotalAmount, err) + } + + return &payment.PaymentNotification{ + TradeNo: notification.TradeNo, + OrderID: notification.OutTradeNo, + Amount: amount, + Status: status, + RawData: rawBody, + }, nil +} + +// Refund requests a refund through Alipay. +func (a *Alipay) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) { + client, err := a.getClient() + if err != nil { + return nil, err + } + + result, err := client.TradeRefund(ctx, alipay.TradeRefund{ + OutTradeNo: req.OrderID, + RefundAmount: req.Amount, + RefundReason: req.Reason, + OutRequestNo: fmt.Sprintf("%s-refund-%d", req.OrderID, time.Now().UnixNano()), + }) + if err != nil { + return nil, fmt.Errorf("alipay TradeRefund: %w", err) + } + + refundStatus := payment.ProviderStatusPending + if result.FundChange == alipayFundChangeYes { + refundStatus = payment.ProviderStatusSuccess + } + + refundID := result.TradeNo + if refundID == "" { + refundID = req.OrderID + alipayRefundSuffix + } + + return &payment.RefundResponse{ + RefundID: refundID, + Status: refundStatus, + }, nil +} + +// CancelPayment closes a pending trade on Alipay. +func (a *Alipay) CancelPayment(ctx context.Context, tradeNo string) error { + client, err := a.getClient() + if err != nil { + return err + } + + _, err = client.TradeClose(ctx, alipay.TradeClose{OutTradeNo: tradeNo}) + if err != nil { + if isTradeNotExist(err) { + return nil + } + return fmt.Errorf("alipay TradeClose: %w", err) + } + return nil +} + +func isTradeNotExist(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), alipayErrTradeNotExist) +} + +// Ensure interface compliance. +var ( + _ payment.Provider = (*Alipay)(nil) + _ payment.CancelableProvider = (*Alipay)(nil) +) diff --git a/backend/internal/payment/provider/alipay_test.go b/backend/internal/payment/provider/alipay_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1b9d66ba9e51fdcef1aae01400541d573f7c4ff9 --- /dev/null +++ b/backend/internal/payment/provider/alipay_test.go @@ -0,0 +1,132 @@ +//go:build unit + +package provider + +import ( + "errors" + "strings" + "testing" +) + +func TestIsTradeNotExist(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + want bool + }{ + { + name: "nil error returns false", + err: nil, + want: false, + }, + { + name: "error containing ACQ.TRADE_NOT_EXIST returns true", + err: errors.New("alipay: sub_code=ACQ.TRADE_NOT_EXIST, sub_msg=交易不存在"), + want: true, + }, + { + name: "error not containing the code returns false", + err: errors.New("alipay: sub_code=ACQ.SYSTEM_ERROR, sub_msg=系统错误"), + want: false, + }, + { + name: "error with only partial match returns false", + err: errors.New("ACQ.TRADE_NOT"), + want: false, + }, + { + name: "error with exact constant value returns true", + err: errors.New(alipayErrTradeNotExist), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := isTradeNotExist(tt.err) + if got != tt.want { + t.Errorf("isTradeNotExist(%v) = %v, want %v", tt.err, got, tt.want) + } + }) + } +} + +func TestNewAlipay(t *testing.T) { + t.Parallel() + + validConfig := map[string]string{ + "appId": "2021001234567890", + "privateKey": "MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...", + } + + // helper to clone and override config fields + withOverride := func(overrides map[string]string) map[string]string { + cfg := make(map[string]string, len(validConfig)) + for k, v := range validConfig { + cfg[k] = v + } + for k, v := range overrides { + cfg[k] = v + } + return cfg + } + + tests := []struct { + name string + config map[string]string + wantErr bool + errSubstr string + }{ + { + name: "valid config succeeds", + config: validConfig, + wantErr: false, + }, + { + name: "missing appId", + config: withOverride(map[string]string{"appId": ""}), + wantErr: true, + errSubstr: "appId", + }, + { + name: "missing privateKey", + config: withOverride(map[string]string{"privateKey": ""}), + wantErr: true, + errSubstr: "privateKey", + }, + { + name: "nil config map returns error for appId", + config: map[string]string{}, + wantErr: true, + errSubstr: "appId", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := NewAlipay("test-instance", tt.config) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) { + t.Errorf("error %q should contain %q", err.Error(), tt.errSubstr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got == nil { + t.Fatal("expected non-nil Alipay instance") + } + if got.instanceID != "test-instance" { + t.Errorf("instanceID = %q, want %q", got.instanceID, "test-instance") + } + }) + } +} diff --git a/backend/internal/payment/provider/easypay.go b/backend/internal/payment/provider/easypay.go new file mode 100644 index 0000000000000000000000000000000000000000..e33a567d0398b240a26144f3f1dd44836748d23d --- /dev/null +++ b/backend/internal/payment/provider/easypay.go @@ -0,0 +1,288 @@ +// Package provider contains concrete payment provider implementations. +package provider + +import ( + "context" + "crypto/hmac" + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +// EasyPay constants. +const ( + easypayCodeSuccess = 1 + easypayStatusPaid = 1 + easypayHTTPTimeout = 10 * time.Second + maxEasypayResponseSize = 1 << 20 // 1MB + tradeStatusSuccess = "TRADE_SUCCESS" + signTypeMD5 = "MD5" + paymentModePopup = "popup" + deviceMobile = "mobile" +) + +// EasyPay implements payment.Provider for the EasyPay aggregation platform. +type EasyPay struct { + instanceID string + config map[string]string + httpClient *http.Client +} + +// NewEasyPay creates a new EasyPay provider. +// config keys: pid, pkey, apiBase, notifyUrl, returnUrl, cid, cidAlipay, cidWxpay +func NewEasyPay(instanceID string, config map[string]string) (*EasyPay, error) { + for _, k := range []string{"pid", "pkey", "apiBase", "notifyUrl", "returnUrl"} { + if config[k] == "" { + return nil, fmt.Errorf("easypay config missing required key: %s", k) + } + } + return &EasyPay{ + instanceID: instanceID, + config: config, + httpClient: &http.Client{Timeout: easypayHTTPTimeout}, + }, nil +} + +func (e *EasyPay) Name() string { return "EasyPay" } +func (e *EasyPay) ProviderKey() string { return payment.TypeEasyPay } +func (e *EasyPay) SupportedTypes() []payment.PaymentType { + return []payment.PaymentType{payment.TypeAlipay, payment.TypeWxpay} +} + +func (e *EasyPay) CreatePayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) { + // Payment mode determined by instance config, not payment type. + // "popup" → hosted page (submit.php); "qrcode"/default → API call (mapi.php). + mode := e.config["paymentMode"] + if mode == paymentModePopup { + return e.createRedirectPayment(req) + } + return e.createAPIPayment(ctx, req) +} + +// createRedirectPayment builds a submit.php URL for browser redirect. +// No server-side API call — the user is redirected to EasyPay's hosted page. +// TradeNo is empty; it arrives via the notify callback after payment. +func (e *EasyPay) createRedirectPayment(req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) { + notifyURL, returnURL := e.resolveURLs(req) + params := map[string]string{ + "pid": e.config["pid"], "type": req.PaymentType, + "out_trade_no": req.OrderID, "notify_url": notifyURL, + "return_url": returnURL, "name": req.Subject, + "money": req.Amount, + } + if cid := e.resolveCID(req.PaymentType); cid != "" { + params["cid"] = cid + } + if req.IsMobile { + params["device"] = deviceMobile + } + params["sign"] = easyPaySign(params, e.config["pkey"]) + params["sign_type"] = signTypeMD5 + + q := url.Values{} + for k, v := range params { + q.Set(k, v) + } + base := strings.TrimRight(e.config["apiBase"], "/") + payURL := base + "/submit.php?" + q.Encode() + return &payment.CreatePaymentResponse{PayURL: payURL}, nil +} + +// createAPIPayment calls mapi.php to get payurl/qrcode (existing behavior). +func (e *EasyPay) createAPIPayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) { + notifyURL, returnURL := e.resolveURLs(req) + params := map[string]string{ + "pid": e.config["pid"], "type": req.PaymentType, + "out_trade_no": req.OrderID, "notify_url": notifyURL, + "return_url": returnURL, "name": req.Subject, + "money": req.Amount, "clientip": req.ClientIP, + } + if cid := e.resolveCID(req.PaymentType); cid != "" { + params["cid"] = cid + } + if req.IsMobile { + params["device"] = deviceMobile + } + params["sign"] = easyPaySign(params, e.config["pkey"]) + params["sign_type"] = signTypeMD5 + + body, err := e.post(ctx, strings.TrimRight(e.config["apiBase"], "/")+"/mapi.php", params) + if err != nil { + return nil, fmt.Errorf("easypay create: %w", err) + } + var resp struct { + Code int `json:"code"` + Msg string `json:"msg"` + TradeNo string `json:"trade_no"` + PayURL string `json:"payurl"` + PayURL2 string `json:"payurl2"` // H5 mobile payment URL + QRCode string `json:"qrcode"` + } + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("easypay parse: %w", err) + } + if resp.Code != easypayCodeSuccess { + return nil, fmt.Errorf("easypay error: %s", resp.Msg) + } + payURL := resp.PayURL + if req.IsMobile && resp.PayURL2 != "" { + payURL = resp.PayURL2 + } + return &payment.CreatePaymentResponse{TradeNo: resp.TradeNo, PayURL: payURL, QRCode: resp.QRCode}, nil +} + +// resolveURLs returns (notifyURL, returnURL) preferring request values, +// falling back to instance config. +func (e *EasyPay) resolveURLs(req payment.CreatePaymentRequest) (string, string) { + notifyURL := req.NotifyURL + if notifyURL == "" { + notifyURL = e.config["notifyUrl"] + } + returnURL := req.ReturnURL + if returnURL == "" { + returnURL = e.config["returnUrl"] + } + return notifyURL, returnURL +} + +func (e *EasyPay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) { + params := map[string]string{ + "act": "order", "pid": e.config["pid"], + "key": e.config["pkey"], "out_trade_no": tradeNo, + } + body, err := e.post(ctx, e.config["apiBase"]+"/api.php", params) + if err != nil { + return nil, fmt.Errorf("easypay query: %w", err) + } + var resp struct { + Code int `json:"code"` + Msg string `json:"msg"` + Status int `json:"status"` + Money string `json:"money"` + } + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("easypay parse query: %w", err) + } + status := payment.ProviderStatusPending + if resp.Status == easypayStatusPaid { + status = payment.ProviderStatusPaid + } + amount, _ := strconv.ParseFloat(resp.Money, 64) + return &payment.QueryOrderResponse{TradeNo: tradeNo, Status: status, Amount: amount}, nil +} + +func (e *EasyPay) VerifyNotification(_ context.Context, rawBody string, _ map[string]string) (*payment.PaymentNotification, error) { + values, err := url.ParseQuery(rawBody) + if err != nil { + return nil, fmt.Errorf("parse notify: %w", err) + } + // url.ParseQuery already decodes values — no additional decode needed. + params := make(map[string]string) + for k := range values { + params[k] = values.Get(k) + } + sign := params["sign"] + if sign == "" { + return nil, fmt.Errorf("missing sign") + } + if !easyPayVerifySign(params, e.config["pkey"], sign) { + return nil, fmt.Errorf("invalid signature") + } + status := payment.ProviderStatusFailed + if params["trade_status"] == tradeStatusSuccess { + status = payment.ProviderStatusSuccess + } + amount, _ := strconv.ParseFloat(params["money"], 64) + return &payment.PaymentNotification{ + TradeNo: params["trade_no"], OrderID: params["out_trade_no"], + Amount: amount, Status: status, RawData: rawBody, + }, nil +} + +func (e *EasyPay) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) { + params := map[string]string{ + "pid": e.config["pid"], "key": e.config["pkey"], + "trade_no": req.TradeNo, "out_trade_no": req.OrderID, "money": req.Amount, + } + body, err := e.post(ctx, e.config["apiBase"]+"/api.php?act=refund", params) + if err != nil { + return nil, fmt.Errorf("easypay refund: %w", err) + } + var resp struct { + Code int `json:"code"` + Msg string `json:"msg"` + } + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("easypay parse refund: %w", err) + } + if resp.Code != easypayCodeSuccess { + return nil, fmt.Errorf("easypay refund failed: %s", resp.Msg) + } + return &payment.RefundResponse{RefundID: req.TradeNo, Status: payment.ProviderStatusSuccess}, nil +} + +func (e *EasyPay) resolveCID(paymentType string) string { + if strings.HasPrefix(paymentType, "alipay") { + if v := e.config["cidAlipay"]; v != "" { + return v + } + return e.config["cid"] + } + if v := e.config["cidWxpay"]; v != "" { + return v + } + return e.config["cid"] +} + +func (e *EasyPay) post(ctx context.Context, endpoint string, params map[string]string) ([]byte, error) { + form := url.Values{} + for k, v := range params { + form.Set(k, v) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(form.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := e.httpClient.Do(req) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + return io.ReadAll(io.LimitReader(resp.Body, maxEasypayResponseSize)) +} + +func easyPaySign(params map[string]string, pkey string) string { + keys := make([]string, 0, len(params)) + for k, v := range params { + if k == "sign" || k == "sign_type" || v == "" { + continue + } + keys = append(keys, k) + } + sort.Strings(keys) + var buf strings.Builder + for i, k := range keys { + if i > 0 { + _ = buf.WriteByte('&') + } + _, _ = buf.WriteString(k + "=" + params[k]) + } + _, _ = buf.WriteString(pkey) + hash := md5.Sum([]byte(buf.String())) + return hex.EncodeToString(hash[:]) +} + +func easyPayVerifySign(params map[string]string, pkey string, sign string) bool { + return hmac.Equal([]byte(easyPaySign(params, pkey)), []byte(sign)) +} diff --git a/backend/internal/payment/provider/easypay_sign_test.go b/backend/internal/payment/provider/easypay_sign_test.go new file mode 100644 index 0000000000000000000000000000000000000000..146a6fa1afd7aea5649cd9d110edcce86dbbdbb0 --- /dev/null +++ b/backend/internal/payment/provider/easypay_sign_test.go @@ -0,0 +1,180 @@ +package provider + +import ( + "testing" +) + +func TestEasyPaySignConsistentOutput(t *testing.T) { + t.Parallel() + + params := map[string]string{ + "pid": "1001", + "type": "alipay", + "out_trade_no": "ORDER123", + "name": "Test Product", + "money": "10.00", + } + pkey := "test_secret_key" + + sign1 := easyPaySign(params, pkey) + sign2 := easyPaySign(params, pkey) + if sign1 != sign2 { + t.Fatalf("easyPaySign should be deterministic: %q != %q", sign1, sign2) + } + if len(sign1) != 32 { + t.Fatalf("MD5 hex should be 32 chars, got %d", len(sign1)) + } +} + +func TestEasyPaySignExcludesSignAndSignType(t *testing.T) { + t.Parallel() + + pkey := "my_key" + base := map[string]string{ + "pid": "1001", + "type": "alipay", + } + withSign := map[string]string{ + "pid": "1001", + "type": "alipay", + "sign": "should_be_ignored", + "sign_type": "MD5", + } + + signBase := easyPaySign(base, pkey) + signWithExtra := easyPaySign(withSign, pkey) + + if signBase != signWithExtra { + t.Fatalf("sign and sign_type should be excluded: base=%q, withExtra=%q", signBase, signWithExtra) + } +} + +func TestEasyPaySignExcludesEmptyValues(t *testing.T) { + t.Parallel() + + pkey := "key123" + base := map[string]string{ + "pid": "1001", + "type": "alipay", + } + withEmpty := map[string]string{ + "pid": "1001", + "type": "alipay", + "device": "", + "clientip": "", + } + + signBase := easyPaySign(base, pkey) + signWithEmpty := easyPaySign(withEmpty, pkey) + + if signBase != signWithEmpty { + t.Fatalf("empty values should be excluded: base=%q, withEmpty=%q", signBase, signWithEmpty) + } +} + +func TestEasyPayVerifySignValid(t *testing.T) { + t.Parallel() + + params := map[string]string{ + "pid": "1001", + "type": "alipay", + "out_trade_no": "ORDER456", + "money": "25.00", + } + pkey := "secret" + + sign := easyPaySign(params, pkey) + + // Add sign to params (as would come in a real callback) + params["sign"] = sign + params["sign_type"] = "MD5" + + if !easyPayVerifySign(params, pkey, sign) { + t.Fatal("easyPayVerifySign should return true for a valid signature") + } +} + +func TestEasyPayVerifySignTampered(t *testing.T) { + t.Parallel() + + params := map[string]string{ + "pid": "1001", + "type": "alipay", + "out_trade_no": "ORDER789", + "money": "50.00", + } + pkey := "secret" + + sign := easyPaySign(params, pkey) + + // Tamper with the amount + params["money"] = "99.99" + + if easyPayVerifySign(params, pkey, sign) { + t.Fatal("easyPayVerifySign should return false for tampered params") + } +} + +func TestEasyPayVerifySignWrongKey(t *testing.T) { + t.Parallel() + + params := map[string]string{ + "pid": "1001", + "type": "wxpay", + } + + sign := easyPaySign(params, "correct_key") + + if easyPayVerifySign(params, "wrong_key", sign) { + t.Fatal("easyPayVerifySign should return false with wrong key") + } +} + +func TestEasyPaySignEmptyParams(t *testing.T) { + t.Parallel() + + sign := easyPaySign(map[string]string{}, "key123") + if sign == "" { + t.Fatal("easyPaySign with empty params should still produce a hash") + } + if len(sign) != 32 { + t.Fatalf("MD5 hex should be 32 chars, got %d", len(sign)) + } +} + +func TestEasyPaySignSortOrder(t *testing.T) { + t.Parallel() + + pkey := "test_key" + params1 := map[string]string{ + "a": "1", + "b": "2", + "c": "3", + } + params2 := map[string]string{ + "c": "3", + "a": "1", + "b": "2", + } + + sign1 := easyPaySign(params1, pkey) + sign2 := easyPaySign(params2, pkey) + + if sign1 != sign2 { + t.Fatalf("easyPaySign should be order-independent: %q != %q", sign1, sign2) + } +} + +func TestEasyPayVerifySignWrongSignValue(t *testing.T) { + t.Parallel() + + params := map[string]string{ + "pid": "1001", + "type": "alipay", + } + pkey := "key" + + if easyPayVerifySign(params, pkey, "00000000000000000000000000000000") { + t.Fatal("easyPayVerifySign should return false for an incorrect sign value") + } +} diff --git a/backend/internal/payment/provider/factory.go b/backend/internal/payment/provider/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..0adbd2676e6dc153b7ce1f86fa382c15427d9955 --- /dev/null +++ b/backend/internal/payment/provider/factory.go @@ -0,0 +1,23 @@ +package provider + +import ( + "fmt" + + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +// CreateProvider creates a Provider from a provider key, instance ID and decrypted config. +func CreateProvider(providerKey string, instanceID string, config map[string]string) (payment.Provider, error) { + switch providerKey { + case payment.TypeEasyPay: + return NewEasyPay(instanceID, config) + case payment.TypeAlipay: + return NewAlipay(instanceID, config) + case payment.TypeWxpay: + return NewWxpay(instanceID, config) + case payment.TypeStripe: + return NewStripe(instanceID, config) + default: + return nil, fmt.Errorf("unknown provider key: %s", providerKey) + } +} diff --git a/backend/internal/payment/provider/stripe.go b/backend/internal/payment/provider/stripe.go new file mode 100644 index 0000000000000000000000000000000000000000..15359d45baf374ad468163a7688698ef8700c744 --- /dev/null +++ b/backend/internal/payment/provider/stripe.go @@ -0,0 +1,262 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "sync" + + "github.com/Wei-Shaw/sub2api/internal/payment" + stripe "github.com/stripe/stripe-go/v85" + "github.com/stripe/stripe-go/v85/webhook" +) + +// Stripe constants. +const ( + stripeCurrency = "cny" + stripeEventPaymentSuccess = "payment_intent.succeeded" + stripeEventPaymentFailed = "payment_intent.payment_failed" +) + +// Stripe implements the payment.CancelableProvider interface for Stripe payments. +type Stripe struct { + instanceID string + config map[string]string + + mu sync.Mutex + initialized bool + sc *stripe.Client +} + +// NewStripe creates a new Stripe provider instance. +func NewStripe(instanceID string, config map[string]string) (*Stripe, error) { + if config["secretKey"] == "" { + return nil, fmt.Errorf("stripe config missing required key: secretKey") + } + return &Stripe{ + instanceID: instanceID, + config: config, + }, nil +} + +func (s *Stripe) ensureInit() { + s.mu.Lock() + defer s.mu.Unlock() + if !s.initialized { + s.sc = stripe.NewClient(s.config["secretKey"]) + s.initialized = true + } +} + +// GetPublishableKey returns the publishable key for frontend use. +func (s *Stripe) GetPublishableKey() string { + return s.config["publishableKey"] +} + +func (s *Stripe) Name() string { return "Stripe" } +func (s *Stripe) ProviderKey() string { return payment.TypeStripe } +func (s *Stripe) SupportedTypes() []payment.PaymentType { + return []payment.PaymentType{payment.TypeStripe} +} + +// stripePaymentMethodTypes maps our PaymentType to Stripe payment_method_types. +var stripePaymentMethodTypes = map[string][]string{ + payment.TypeCard: {"card"}, + payment.TypeAlipay: {"alipay"}, + payment.TypeWxpay: {"wechat_pay"}, + payment.TypeLink: {"link"}, +} + +// CreatePayment creates a Stripe PaymentIntent. +func (s *Stripe) CreatePayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) { + s.ensureInit() + + amountInCents, err := payment.YuanToFen(req.Amount) + if err != nil { + return nil, fmt.Errorf("stripe create payment: %w", err) + } + + // Collect all Stripe payment_method_types from the instance's configured sub-methods + methods := resolveStripeMethodTypes(req.InstanceSubMethods) + + pmTypes := make([]*string, len(methods)) + for i, m := range methods { + pmTypes[i] = stripe.String(m) + } + + params := &stripe.PaymentIntentCreateParams{ + Amount: stripe.Int64(amountInCents), + Currency: stripe.String(stripeCurrency), + PaymentMethodTypes: pmTypes, + Description: stripe.String(req.Subject), + Metadata: map[string]string{"orderId": req.OrderID}, + } + + // WeChat Pay requires payment_method_options with client type + if hasStripeMethod(methods, "wechat_pay") { + params.PaymentMethodOptions = &stripe.PaymentIntentCreatePaymentMethodOptionsParams{ + WeChatPay: &stripe.PaymentIntentCreatePaymentMethodOptionsWeChatPayParams{ + Client: stripe.String("web"), + }, + } + } + + params.SetIdempotencyKey(fmt.Sprintf("pi-%s", req.OrderID)) + params.Context = ctx + + pi, err := s.sc.V1PaymentIntents.Create(ctx, params) + if err != nil { + return nil, fmt.Errorf("stripe create payment: %w", err) + } + + return &payment.CreatePaymentResponse{ + TradeNo: pi.ID, + ClientSecret: pi.ClientSecret, + }, nil +} + +// QueryOrder retrieves a PaymentIntent by ID. +func (s *Stripe) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) { + s.ensureInit() + + pi, err := s.sc.V1PaymentIntents.Retrieve(ctx, tradeNo, nil) + if err != nil { + return nil, fmt.Errorf("stripe query order: %w", err) + } + + status := payment.ProviderStatusPending + switch pi.Status { + case stripe.PaymentIntentStatusSucceeded: + status = payment.ProviderStatusPaid + case stripe.PaymentIntentStatusCanceled: + status = payment.ProviderStatusFailed + } + + return &payment.QueryOrderResponse{ + TradeNo: pi.ID, + Status: status, + Amount: payment.FenToYuan(pi.Amount), + }, nil +} + +// VerifyNotification verifies a Stripe webhook event. +func (s *Stripe) VerifyNotification(_ context.Context, rawBody string, headers map[string]string) (*payment.PaymentNotification, error) { + s.ensureInit() + + webhookSecret := s.config["webhookSecret"] + if webhookSecret == "" { + return nil, fmt.Errorf("stripe webhookSecret not configured") + } + + sig := headers["stripe-signature"] + if sig == "" { + return nil, fmt.Errorf("stripe notification missing stripe-signature header") + } + + event, err := webhook.ConstructEvent([]byte(rawBody), sig, webhookSecret) + if err != nil { + return nil, fmt.Errorf("stripe verify notification: %w", err) + } + + switch event.Type { + case stripeEventPaymentSuccess: + return parseStripePaymentIntent(&event, payment.ProviderStatusSuccess, rawBody) + case stripeEventPaymentFailed: + return parseStripePaymentIntent(&event, payment.ProviderStatusFailed, rawBody) + } + + return nil, nil +} + +func parseStripePaymentIntent(event *stripe.Event, status string, rawBody string) (*payment.PaymentNotification, error) { + var pi stripe.PaymentIntent + if err := json.Unmarshal(event.Data.Raw, &pi); err != nil { + return nil, fmt.Errorf("stripe parse payment_intent: %w", err) + } + return &payment.PaymentNotification{ + TradeNo: pi.ID, + OrderID: pi.Metadata["orderId"], + Amount: payment.FenToYuan(pi.Amount), + Status: status, + RawData: rawBody, + }, nil +} + +// Refund creates a Stripe refund. +func (s *Stripe) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) { + s.ensureInit() + + amountInCents, err := payment.YuanToFen(req.Amount) + if err != nil { + return nil, fmt.Errorf("stripe refund: %w", err) + } + + params := &stripe.RefundCreateParams{ + PaymentIntent: stripe.String(req.TradeNo), + Amount: stripe.Int64(amountInCents), + Reason: stripe.String(string(stripe.RefundReasonRequestedByCustomer)), + } + params.Context = ctx + + r, err := s.sc.V1Refunds.Create(ctx, params) + if err != nil { + return nil, fmt.Errorf("stripe refund: %w", err) + } + + refundStatus := payment.ProviderStatusPending + if r.Status == stripe.RefundStatusSucceeded { + refundStatus = payment.ProviderStatusSuccess + } + + return &payment.RefundResponse{ + RefundID: r.ID, + Status: refundStatus, + }, nil +} + +// resolveStripeMethodTypes converts instance supported_types (comma-separated) +// into Stripe API payment_method_types. Falls back to ["card"] if empty. +func resolveStripeMethodTypes(instanceSubMethods string) []string { + if instanceSubMethods == "" { + return []string{"card"} + } + var methods []string + for _, t := range strings.Split(instanceSubMethods, ",") { + t = strings.TrimSpace(t) + if mapped, ok := stripePaymentMethodTypes[t]; ok { + methods = append(methods, mapped...) + } + } + if len(methods) == 0 { + return []string{"card"} + } + return methods +} + +// hasStripeMethod checks if the given Stripe method list contains the target method. +func hasStripeMethod(methods []string, target string) bool { + for _, m := range methods { + if m == target { + return true + } + } + return false +} + +// CancelPayment cancels a pending PaymentIntent. +func (s *Stripe) CancelPayment(ctx context.Context, tradeNo string) error { + s.ensureInit() + + _, err := s.sc.V1PaymentIntents.Cancel(ctx, tradeNo, nil) + if err != nil { + return fmt.Errorf("stripe cancel payment: %w", err) + } + return nil +} + +// Ensure interface compliance. +var ( + _ payment.Provider = (*Stripe)(nil) + _ payment.CancelableProvider = (*Stripe)(nil) +) diff --git a/backend/internal/payment/provider/wxpay.go b/backend/internal/payment/provider/wxpay.go new file mode 100644 index 0000000000000000000000000000000000000000..14e51cd206bc2e6ca732bbd6146964f8a120724c --- /dev/null +++ b/backend/internal/payment/provider/wxpay.go @@ -0,0 +1,350 @@ +package provider + +import ( + "bytes" + "context" + "crypto/rsa" + "fmt" + "io" + "log/slog" + "net/http" + "strings" + "sync" + "time" + + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers" + "github.com/wechatpay-apiv3/wechatpay-go/core/notify" + "github.com/wechatpay-apiv3/wechatpay-go/core/option" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/h5" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" + "github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic" + "github.com/wechatpay-apiv3/wechatpay-go/utils" +) + +// WeChat Pay constants. +const ( + wxpayCurrency = "CNY" + wxpayH5Type = "Wap" +) + +// WeChat Pay trade states. +const ( + wxpayTradeStateSuccess = "SUCCESS" + wxpayTradeStateRefund = "REFUND" + wxpayTradeStateClosed = "CLOSED" + wxpayTradeStatePayError = "PAYERROR" +) + +// WeChat Pay notification event types. +const ( + wxpayEventTransactionSuccess = "TRANSACTION.SUCCESS" +) + +// WeChat Pay error codes. +const ( + wxpayErrNoAuth = "NO_AUTH" +) + +type Wxpay struct { + instanceID string + config map[string]string + mu sync.Mutex + coreClient *core.Client + notifyHandler *notify.Handler +} + +func NewWxpay(instanceID string, config map[string]string) (*Wxpay, error) { + required := []string{"appId", "mchId", "privateKey", "apiV3Key", "publicKey", "publicKeyId", "certSerial"} + for _, k := range required { + if config[k] == "" { + return nil, fmt.Errorf("wxpay config missing required key: %s", k) + } + } + if len(config["apiV3Key"]) != 32 { + return nil, fmt.Errorf("wxpay apiV3Key must be exactly 32 bytes, got %d", len(config["apiV3Key"])) + } + return &Wxpay{instanceID: instanceID, config: config}, nil +} + +func (w *Wxpay) Name() string { return "Wxpay" } +func (w *Wxpay) ProviderKey() string { return payment.TypeWxpay } +func (w *Wxpay) SupportedTypes() []payment.PaymentType { + return []payment.PaymentType{payment.TypeWxpayDirect} +} + +func formatPEM(key, keyType string) string { + key = strings.TrimSpace(key) + if strings.HasPrefix(key, "-----BEGIN") { + return key + } + return fmt.Sprintf("-----BEGIN %s-----\n%s\n-----END %s-----", keyType, key, keyType) +} + +func (w *Wxpay) ensureClient() (*core.Client, error) { + w.mu.Lock() + defer w.mu.Unlock() + if w.coreClient != nil { + return w.coreClient, nil + } + privateKey, publicKey, err := w.loadKeyPair() + if err != nil { + return nil, err + } + certSerial := w.config["certSerial"] + verifier := verifiers.NewSHA256WithRSAPubkeyVerifier(w.config["publicKeyId"], *publicKey) + client, err := core.NewClient(context.Background(), + option.WithMerchantCredential(w.config["mchId"], certSerial, privateKey), + option.WithVerifier(verifier)) + if err != nil { + return nil, fmt.Errorf("wxpay init client: %w", err) + } + handler, err := notify.NewRSANotifyHandler(w.config["apiV3Key"], verifier) + if err != nil { + return nil, fmt.Errorf("wxpay init notify handler: %w", err) + } + w.notifyHandler = handler + w.coreClient = client + return w.coreClient, nil +} + +func (w *Wxpay) loadKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) { + privateKey, err := utils.LoadPrivateKey(formatPEM(w.config["privateKey"], "PRIVATE KEY")) + if err != nil { + return nil, nil, fmt.Errorf("wxpay load private key: %w", err) + } + publicKey, err := utils.LoadPublicKey(formatPEM(w.config["publicKey"], "PUBLIC KEY")) + if err != nil { + return nil, nil, fmt.Errorf("wxpay load public key: %w", err) + } + return privateKey, publicKey, nil +} + +func (w *Wxpay) CreatePayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) { + client, err := w.ensureClient() + if err != nil { + return nil, err + } + // Request-first, config-fallback (consistent with EasyPay/Alipay) + notifyURL := req.NotifyURL + if notifyURL == "" { + notifyURL = w.config["notifyUrl"] + } + if notifyURL == "" { + return nil, fmt.Errorf("wxpay notifyUrl is required") + } + totalFen, err := payment.YuanToFen(req.Amount) + if err != nil { + return nil, fmt.Errorf("wxpay create payment: %w", err) + } + if req.IsMobile && req.ClientIP != "" { + resp, err := w.createOrder(ctx, client, req, notifyURL, totalFen, true) + if err == nil { + return resp, nil + } + if !strings.Contains(err.Error(), wxpayErrNoAuth) { + return nil, err + } + slog.Warn("wxpay H5 payment not authorized, falling back to native", "order", req.OrderID) + } + return w.createOrder(ctx, client, req, notifyURL, totalFen, false) +} + +func (w *Wxpay) createOrder(ctx context.Context, c *core.Client, req payment.CreatePaymentRequest, notifyURL string, totalFen int64, useH5 bool) (*payment.CreatePaymentResponse, error) { + if useH5 { + return w.prepayH5(ctx, c, req, notifyURL, totalFen) + } + return w.prepayNative(ctx, c, req, notifyURL, totalFen) +} + +func (w *Wxpay) prepayNative(ctx context.Context, c *core.Client, req payment.CreatePaymentRequest, notifyURL string, totalFen int64) (*payment.CreatePaymentResponse, error) { + svc := native.NativeApiService{Client: c} + cur := wxpayCurrency + resp, _, err := svc.Prepay(ctx, native.PrepayRequest{ + Appid: core.String(w.config["appId"]), Mchid: core.String(w.config["mchId"]), + Description: core.String(req.Subject), OutTradeNo: core.String(req.OrderID), + NotifyUrl: core.String(notifyURL), + Amount: &native.Amount{Total: core.Int64(totalFen), Currency: &cur}, + }) + if err != nil { + return nil, fmt.Errorf("wxpay native prepay: %w", err) + } + codeURL := "" + if resp.CodeUrl != nil { + codeURL = *resp.CodeUrl + } + return &payment.CreatePaymentResponse{TradeNo: req.OrderID, QRCode: codeURL}, nil +} + +func (w *Wxpay) prepayH5(ctx context.Context, c *core.Client, req payment.CreatePaymentRequest, notifyURL string, totalFen int64) (*payment.CreatePaymentResponse, error) { + svc := h5.H5ApiService{Client: c} + cur := wxpayCurrency + tp := wxpayH5Type + resp, _, err := svc.Prepay(ctx, h5.PrepayRequest{ + Appid: core.String(w.config["appId"]), Mchid: core.String(w.config["mchId"]), + Description: core.String(req.Subject), OutTradeNo: core.String(req.OrderID), + NotifyUrl: core.String(notifyURL), + Amount: &h5.Amount{Total: core.Int64(totalFen), Currency: &cur}, + SceneInfo: &h5.SceneInfo{PayerClientIp: core.String(req.ClientIP), H5Info: &h5.H5Info{Type: &tp}}, + }) + if err != nil { + return nil, fmt.Errorf("wxpay h5 prepay: %w", err) + } + h5URL := "" + if resp.H5Url != nil { + h5URL = *resp.H5Url + } + return &payment.CreatePaymentResponse{TradeNo: req.OrderID, PayURL: h5URL}, nil +} + +func wxSV(s *string) string { + if s == nil { + return "" + } + return *s +} + +func mapWxState(s string) string { + switch s { + case wxpayTradeStateSuccess: + return payment.ProviderStatusPaid + case wxpayTradeStateRefund: + return payment.ProviderStatusRefunded + case wxpayTradeStateClosed, wxpayTradeStatePayError: + return payment.ProviderStatusFailed + default: + return payment.ProviderStatusPending + } +} + +func (w *Wxpay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) { + c, err := w.ensureClient() + if err != nil { + return nil, err + } + svc := native.NativeApiService{Client: c} + tx, _, err := svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{ + OutTradeNo: core.String(tradeNo), Mchid: core.String(w.config["mchId"]), + }) + if err != nil { + return nil, fmt.Errorf("wxpay query order: %w", err) + } + var amt float64 + if tx.Amount != nil && tx.Amount.Total != nil { + amt = payment.FenToYuan(*tx.Amount.Total) + } + id := tradeNo + if tx.TransactionId != nil { + id = *tx.TransactionId + } + pa := "" + if tx.SuccessTime != nil { + pa = *tx.SuccessTime + } + return &payment.QueryOrderResponse{TradeNo: id, Status: mapWxState(wxSV(tx.TradeState)), Amount: amt, PaidAt: pa}, nil +} + +func (w *Wxpay) VerifyNotification(ctx context.Context, rawBody string, headers map[string]string) (*payment.PaymentNotification, error) { + if _, err := w.ensureClient(); err != nil { + return nil, err + } + r, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", io.NopCloser(bytes.NewBufferString(rawBody))) + if err != nil { + return nil, fmt.Errorf("wxpay construct request: %w", err) + } + for k, v := range headers { + r.Header.Set(k, v) + } + var tx payments.Transaction + nr, err := w.notifyHandler.ParseNotifyRequest(ctx, r, &tx) + if err != nil { + return nil, fmt.Errorf("wxpay verify notification: %w", err) + } + if nr.EventType != wxpayEventTransactionSuccess { + return nil, nil + } + var amt float64 + if tx.Amount != nil && tx.Amount.Total != nil { + amt = payment.FenToYuan(*tx.Amount.Total) + } + st := payment.ProviderStatusFailed + if wxSV(tx.TradeState) == wxpayTradeStateSuccess { + st = payment.ProviderStatusSuccess + } + return &payment.PaymentNotification{ + TradeNo: wxSV(tx.TransactionId), OrderID: wxSV(tx.OutTradeNo), + Amount: amt, Status: st, RawData: rawBody, + }, nil +} + +func (w *Wxpay) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) { + c, err := w.ensureClient() + if err != nil { + return nil, err + } + rf, err := payment.YuanToFen(req.Amount) + if err != nil { + return nil, fmt.Errorf("wxpay refund amount: %w", err) + } + tf, err := w.queryOrderTotalFen(ctx, c, req.OrderID) + if err != nil { + return nil, err + } + rs := refunddomestic.RefundsApiService{Client: c} + cur := wxpayCurrency + res, _, err := rs.Create(ctx, refunddomestic.CreateRequest{ + OutTradeNo: core.String(req.OrderID), + OutRefundNo: core.String(fmt.Sprintf("%s-refund-%d", req.OrderID, time.Now().UnixNano())), + Reason: core.String(req.Reason), + Amount: &refunddomestic.AmountReq{Refund: core.Int64(rf), Total: core.Int64(tf), Currency: &cur}, + }) + if err != nil { + return nil, fmt.Errorf("wxpay refund: %w", err) + } + rid := wxSV(res.RefundId) + if rid == "" { + rid = fmt.Sprintf("%s-refund", req.OrderID) + } + st := payment.ProviderStatusPending + if res.Status != nil && *res.Status == refunddomestic.STATUS_SUCCESS { + st = payment.ProviderStatusSuccess + } + return &payment.RefundResponse{RefundID: rid, Status: st}, nil +} + +func (w *Wxpay) queryOrderTotalFen(ctx context.Context, c *core.Client, orderID string) (int64, error) { + svc := native.NativeApiService{Client: c} + tx, _, err := svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{ + OutTradeNo: core.String(orderID), Mchid: core.String(w.config["mchId"]), + }) + if err != nil { + return 0, fmt.Errorf("wxpay refund query order: %w", err) + } + var tf int64 + if tx.Amount != nil && tx.Amount.Total != nil { + tf = *tx.Amount.Total + } + return tf, nil +} + +func (w *Wxpay) CancelPayment(ctx context.Context, tradeNo string) error { + c, err := w.ensureClient() + if err != nil { + return err + } + svc := native.NativeApiService{Client: c} + _, err = svc.CloseOrder(ctx, native.CloseOrderRequest{ + OutTradeNo: core.String(tradeNo), Mchid: core.String(w.config["mchId"]), + }) + if err != nil { + return fmt.Errorf("wxpay cancel payment: %w", err) + } + return nil +} + +var ( + _ payment.Provider = (*Wxpay)(nil) + _ payment.CancelableProvider = (*Wxpay)(nil) +) diff --git a/backend/internal/payment/provider/wxpay_test.go b/backend/internal/payment/provider/wxpay_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b8b99537457f18701efdec12fc7cf7b06626b328 --- /dev/null +++ b/backend/internal/payment/provider/wxpay_test.go @@ -0,0 +1,259 @@ +//go:build unit + +package provider + +import ( + "strings" + "testing" + + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +func TestMapWxState(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + want string + }{ + { + name: "SUCCESS maps to paid", + input: wxpayTradeStateSuccess, + want: payment.ProviderStatusPaid, + }, + { + name: "REFUND maps to refunded", + input: wxpayTradeStateRefund, + want: payment.ProviderStatusRefunded, + }, + { + name: "CLOSED maps to failed", + input: wxpayTradeStateClosed, + want: payment.ProviderStatusFailed, + }, + { + name: "PAYERROR maps to failed", + input: wxpayTradeStatePayError, + want: payment.ProviderStatusFailed, + }, + { + name: "unknown state maps to pending", + input: "NOTPAY", + want: payment.ProviderStatusPending, + }, + { + name: "empty string maps to pending", + input: "", + want: payment.ProviderStatusPending, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := mapWxState(tt.input) + if got != tt.want { + t.Errorf("mapWxState(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + +func TestWxSV(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input *string + want string + }{ + { + name: "nil pointer returns empty string", + input: nil, + want: "", + }, + { + name: "non-nil pointer returns value", + input: strPtr("hello"), + want: "hello", + }, + { + name: "pointer to empty string returns empty string", + input: strPtr(""), + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := wxSV(tt.input) + if got != tt.want { + t.Errorf("wxSV() = %q, want %q", got, tt.want) + } + }) + } +} + +func strPtr(s string) *string { + return &s +} + +func TestFormatPEM(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + key string + keyType string + want string + }{ + { + name: "raw key gets wrapped with headers", + key: "MIIBIjANBgkqhki...", + keyType: "PUBLIC KEY", + want: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----", + }, + { + name: "already formatted key is returned as-is", + key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----", + keyType: "PRIVATE KEY", + want: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----", + }, + { + name: "key with leading/trailing whitespace is trimmed before check", + key: " \n MIIBIjANBgkqhki... \n ", + keyType: "PUBLIC KEY", + want: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----", + }, + { + name: "already formatted key with whitespace is trimmed and returned", + key: " -----BEGIN RSA PRIVATE KEY-----\ndata\n-----END RSA PRIVATE KEY----- ", + keyType: "RSA PRIVATE KEY", + want: "-----BEGIN RSA PRIVATE KEY-----\ndata\n-----END RSA PRIVATE KEY-----", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := formatPEM(tt.key, tt.keyType) + if got != tt.want { + t.Errorf("formatPEM(%q, %q) =\n%s\nwant:\n%s", tt.key, tt.keyType, got, tt.want) + } + }) + } +} + +func TestNewWxpay(t *testing.T) { + t.Parallel() + + validConfig := map[string]string{ + "appId": "wx1234567890", + "mchId": "1234567890", + "privateKey": "fake-private-key", + "apiV3Key": "12345678901234567890123456789012", // exactly 32 bytes + "publicKey": "fake-public-key", + "publicKeyId": "key-id-001", + "certSerial": "SERIAL001", + } + + // helper to clone and override config fields + withOverride := func(overrides map[string]string) map[string]string { + cfg := make(map[string]string, len(validConfig)) + for k, v := range validConfig { + cfg[k] = v + } + for k, v := range overrides { + cfg[k] = v + } + return cfg + } + + tests := []struct { + name string + config map[string]string + wantErr bool + errSubstr string + }{ + { + name: "valid config succeeds", + config: validConfig, + wantErr: false, + }, + { + name: "missing appId", + config: withOverride(map[string]string{"appId": ""}), + wantErr: true, + errSubstr: "appId", + }, + { + name: "missing mchId", + config: withOverride(map[string]string{"mchId": ""}), + wantErr: true, + errSubstr: "mchId", + }, + { + name: "missing privateKey", + config: withOverride(map[string]string{"privateKey": ""}), + wantErr: true, + errSubstr: "privateKey", + }, + { + name: "missing apiV3Key", + config: withOverride(map[string]string{"apiV3Key": ""}), + wantErr: true, + errSubstr: "apiV3Key", + }, + { + name: "missing publicKey", + config: withOverride(map[string]string{"publicKey": ""}), + wantErr: true, + errSubstr: "publicKey", + }, + { + name: "missing publicKeyId", + config: withOverride(map[string]string{"publicKeyId": ""}), + wantErr: true, + errSubstr: "publicKeyId", + }, + { + name: "apiV3Key too short", + config: withOverride(map[string]string{"apiV3Key": "short"}), + wantErr: true, + errSubstr: "exactly 32 bytes", + }, + { + name: "apiV3Key too long", + config: withOverride(map[string]string{"apiV3Key": "123456789012345678901234567890123"}), // 33 bytes + wantErr: true, + errSubstr: "exactly 32 bytes", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := NewWxpay("test-instance", tt.config) + if tt.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) { + t.Errorf("error %q should contain %q", err.Error(), tt.errSubstr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got == nil { + t.Fatal("expected non-nil Wxpay instance") + } + if got.instanceID != "test-instance" { + t.Errorf("instanceID = %q, want %q", got.instanceID, "test-instance") + } + }) + } +} diff --git a/backend/internal/payment/registry.go b/backend/internal/payment/registry.go new file mode 100644 index 0000000000000000000000000000000000000000..259eb4bb3357b54c08f4c59b37260409ef2d4bfc --- /dev/null +++ b/backend/internal/payment/registry.go @@ -0,0 +1,85 @@ +package payment + +import ( + "sync" + + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// Registry is a thread-safe registry mapping PaymentType to Provider. +type Registry struct { + mu sync.RWMutex + providers map[PaymentType]Provider +} + +// ErrProviderNotFound is returned when a requested payment provider is not registered. +var ErrProviderNotFound = infraerrors.NotFound("PROVIDER_NOT_FOUND", "payment provider not registered") + +// NewRegistry creates a new empty provider registry. +func NewRegistry() *Registry { + return &Registry{ + providers: make(map[PaymentType]Provider), + } +} + +// Register adds a provider for each of its supported payment types. +// If a type was previously registered, it is overwritten. +func (r *Registry) Register(p Provider) { + r.mu.Lock() + defer r.mu.Unlock() + for _, t := range p.SupportedTypes() { + r.providers[t] = p + } +} + +// GetProvider returns the provider registered for the given payment type. +func (r *Registry) GetProvider(t PaymentType) (Provider, error) { + r.mu.RLock() + defer r.mu.RUnlock() + p, ok := r.providers[t] + if !ok { + return nil, ErrProviderNotFound + } + return p, nil +} + +// GetProviderByKey returns the first provider whose ProviderKey matches the given key. +func (r *Registry) GetProviderByKey(key string) (Provider, error) { + r.mu.RLock() + defer r.mu.RUnlock() + for _, p := range r.providers { + if p.ProviderKey() == key { + return p, nil + } + } + return nil, ErrProviderNotFound +} + +// GetProviderKey returns the provider key for the given payment type, or empty string if not found. +func (r *Registry) GetProviderKey(t PaymentType) string { + r.mu.RLock() + defer r.mu.RUnlock() + p, ok := r.providers[t] + if !ok { + return "" + } + return p.ProviderKey() +} + +// SupportedTypes returns all currently registered payment types. +func (r *Registry) SupportedTypes() []PaymentType { + r.mu.RLock() + defer r.mu.RUnlock() + types := make([]PaymentType, 0, len(r.providers)) + for t := range r.providers { + types = append(types, t) + } + return types +} + +// Clear removes all registered providers. +func (r *Registry) Clear() { + r.mu.Lock() + defer r.mu.Unlock() + r.providers = make(map[PaymentType]Provider) +} diff --git a/backend/internal/payment/registry_test.go b/backend/internal/payment/registry_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9684945c761093f8e505b35ef04abdbca0f0ea91 --- /dev/null +++ b/backend/internal/payment/registry_test.go @@ -0,0 +1,234 @@ +package payment + +import ( + "context" + "fmt" + "sync" + "testing" +) + +// mockProvider implements the Provider interface for testing. +type mockProvider struct { + name string + key string + supportedTypes []PaymentType +} + +func (m *mockProvider) Name() string { return m.name } +func (m *mockProvider) ProviderKey() string { return m.key } +func (m *mockProvider) SupportedTypes() []PaymentType { return m.supportedTypes } +func (m *mockProvider) CreatePayment(_ context.Context, _ CreatePaymentRequest) (*CreatePaymentResponse, error) { + return nil, nil +} +func (m *mockProvider) QueryOrder(_ context.Context, _ string) (*QueryOrderResponse, error) { + return nil, nil +} +func (m *mockProvider) VerifyNotification(_ context.Context, _ string, _ map[string]string) (*PaymentNotification, error) { + return nil, nil +} +func (m *mockProvider) Refund(_ context.Context, _ RefundRequest) (*RefundResponse, error) { + return nil, nil +} + +func TestRegistryRegisterAndGetProvider(t *testing.T) { + t.Parallel() + r := NewRegistry() + + p := &mockProvider{ + name: "TestPay", + key: "testpay", + supportedTypes: []PaymentType{TypeAlipay, TypeWxpay}, + } + r.Register(p) + + got, err := r.GetProvider(TypeAlipay) + if err != nil { + t.Fatalf("GetProvider(alipay) error: %v", err) + } + if got.ProviderKey() != "testpay" { + t.Fatalf("GetProvider(alipay) key = %q, want %q", got.ProviderKey(), "testpay") + } + + got2, err := r.GetProvider(TypeWxpay) + if err != nil { + t.Fatalf("GetProvider(wxpay) error: %v", err) + } + if got2.ProviderKey() != "testpay" { + t.Fatalf("GetProvider(wxpay) key = %q, want %q", got2.ProviderKey(), "testpay") + } +} + +func TestRegistryGetProviderNotFound(t *testing.T) { + t.Parallel() + r := NewRegistry() + + _, err := r.GetProvider("nonexistent") + if err == nil { + t.Fatal("GetProvider for unregistered type should return error") + } +} + +func TestRegistryGetProviderByKey(t *testing.T) { + t.Parallel() + r := NewRegistry() + + p := &mockProvider{ + name: "EasyPay", + key: "easypay", + supportedTypes: []PaymentType{TypeAlipay}, + } + r.Register(p) + + got, err := r.GetProviderByKey("easypay") + if err != nil { + t.Fatalf("GetProviderByKey error: %v", err) + } + if got.Name() != "EasyPay" { + t.Fatalf("GetProviderByKey name = %q, want %q", got.Name(), "EasyPay") + } +} + +func TestRegistryGetProviderByKeyNotFound(t *testing.T) { + t.Parallel() + r := NewRegistry() + + _, err := r.GetProviderByKey("nonexistent") + if err == nil { + t.Fatal("GetProviderByKey for unknown key should return error") + } +} + +func TestRegistryGetProviderKeyUnknownType(t *testing.T) { + t.Parallel() + r := NewRegistry() + + key := r.GetProviderKey("unknown_type") + if key != "" { + t.Fatalf("GetProviderKey for unknown type should return empty, got %q", key) + } +} + +func TestRegistryGetProviderKeyKnownType(t *testing.T) { + t.Parallel() + r := NewRegistry() + + p := &mockProvider{ + name: "Stripe", + key: "stripe", + supportedTypes: []PaymentType{TypeStripe}, + } + r.Register(p) + + key := r.GetProviderKey(TypeStripe) + if key != "stripe" { + t.Fatalf("GetProviderKey(stripe) = %q, want %q", key, "stripe") + } +} + +func TestRegistrySupportedTypes(t *testing.T) { + t.Parallel() + r := NewRegistry() + + p1 := &mockProvider{ + name: "EasyPay", + key: "easypay", + supportedTypes: []PaymentType{TypeAlipay, TypeWxpay}, + } + p2 := &mockProvider{ + name: "Stripe", + key: "stripe", + supportedTypes: []PaymentType{TypeStripe}, + } + r.Register(p1) + r.Register(p2) + + types := r.SupportedTypes() + if len(types) != 3 { + t.Fatalf("SupportedTypes() len = %d, want 3", len(types)) + } + + typeSet := make(map[PaymentType]bool) + for _, tp := range types { + typeSet[tp] = true + } + for _, expected := range []PaymentType{TypeAlipay, TypeWxpay, TypeStripe} { + if !typeSet[expected] { + t.Fatalf("SupportedTypes() missing %q", expected) + } + } +} + +func TestRegistrySupportedTypesEmpty(t *testing.T) { + t.Parallel() + r := NewRegistry() + + types := r.SupportedTypes() + if len(types) != 0 { + t.Fatalf("SupportedTypes() on empty registry should be empty, got %d", len(types)) + } +} + +func TestRegistryOverwriteExisting(t *testing.T) { + t.Parallel() + r := NewRegistry() + + p1 := &mockProvider{ + name: "OldPay", + key: "old", + supportedTypes: []PaymentType{TypeAlipay}, + } + p2 := &mockProvider{ + name: "NewPay", + key: "new", + supportedTypes: []PaymentType{TypeAlipay}, + } + r.Register(p1) + r.Register(p2) + + got, err := r.GetProvider(TypeAlipay) + if err != nil { + t.Fatalf("GetProvider error: %v", err) + } + if got.Name() != "NewPay" { + t.Fatalf("expected overwritten provider, got %q", got.Name()) + } +} + +func TestRegistryConcurrentAccess(t *testing.T) { + t.Parallel() + r := NewRegistry() + + const goroutines = 50 + var wg sync.WaitGroup + wg.Add(goroutines * 2) + + // Concurrent writers + for i := 0; i < goroutines; i++ { + go func(idx int) { + defer wg.Done() + p := &mockProvider{ + name: fmt.Sprintf("Provider-%d", idx), + key: fmt.Sprintf("key-%d", idx), + supportedTypes: []PaymentType{PaymentType(fmt.Sprintf("type-%d", idx))}, + } + r.Register(p) + }(i) + } + + // Concurrent readers + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + _ = r.SupportedTypes() + _, _ = r.GetProvider("some-type") + _ = r.GetProviderKey("some-type") + }() + } + + wg.Wait() + + types := r.SupportedTypes() + if len(types) != goroutines { + t.Fatalf("after concurrent registration, expected %d types, got %d", goroutines, len(types)) + } +} diff --git a/backend/internal/payment/types.go b/backend/internal/payment/types.go new file mode 100644 index 0000000000000000000000000000000000000000..c413d8f3ee249fa6792ca6f2a434cdf58fada17f --- /dev/null +++ b/backend/internal/payment/types.go @@ -0,0 +1,180 @@ +// Package payment provides the core payment provider abstraction, +// registry, load balancing, and shared utilities for the payment subsystem. +package payment + +import "context" + +// PaymentType represents a supported payment method. +type PaymentType = string + +// Supported payment type constants. +const ( + TypeAlipay PaymentType = "alipay" + TypeWxpay PaymentType = "wxpay" + TypeAlipayDirect PaymentType = "alipay_direct" + TypeWxpayDirect PaymentType = "wxpay_direct" + TypeStripe PaymentType = "stripe" + TypeCard PaymentType = "card" + TypeLink PaymentType = "link" + TypeEasyPay PaymentType = "easypay" +) + +// Order status constants shared across payment and service layers. +const ( + OrderStatusPending = "PENDING" + OrderStatusPaid = "PAID" + OrderStatusRecharging = "RECHARGING" + OrderStatusCompleted = "COMPLETED" + OrderStatusExpired = "EXPIRED" + OrderStatusCancelled = "CANCELLED" + OrderStatusFailed = "FAILED" + OrderStatusRefundRequested = "REFUND_REQUESTED" + OrderStatusRefunding = "REFUNDING" + OrderStatusPartiallyRefunded = "PARTIALLY_REFUNDED" + OrderStatusRefunded = "REFUNDED" + OrderStatusRefundFailed = "REFUND_FAILED" +) + +// Order types distinguish balance recharges from subscription purchases. +const ( + OrderTypeBalance = "balance" + OrderTypeSubscription = "subscription" +) + +// Entity statuses shared across users, groups, etc. +const ( + EntityStatusActive = "active" +) + +// Deduction types for refund flow. +const ( + DeductionTypeBalance = "balance" + DeductionTypeSubscription = "subscription" + DeductionTypeNone = "none" +) + +// Payment notification status values. +const ( + NotificationStatusSuccess = "success" + NotificationStatusPaid = "paid" +) + +// Provider-level status constants returned by provider implementations +// to the service layer (lowercase, distinct from OrderStatus uppercase constants). +const ( + ProviderStatusPending = "pending" + ProviderStatusPaid = "paid" + ProviderStatusSuccess = "success" + ProviderStatusFailed = "failed" + ProviderStatusRefunded = "refunded" +) + +// DefaultLoadBalanceStrategy is the default load-balancing strategy +// used when no strategy is configured. +const DefaultLoadBalanceStrategy = "round-robin" + +// ConfigKeyPublishableKey is the config map key for Stripe's publishable key. +const ConfigKeyPublishableKey = "publishableKey" + +// GetBasePaymentType extracts the base payment method from a composite key. +// For example, "alipay_direct" -> "alipay". +func GetBasePaymentType(t string) string { + switch { + case t == TypeEasyPay: + return TypeEasyPay + case t == TypeStripe || t == TypeCard || t == TypeLink: + return TypeStripe + case len(t) >= len(TypeAlipay) && t[:len(TypeAlipay)] == TypeAlipay: + return TypeAlipay + case len(t) >= len(TypeWxpay) && t[:len(TypeWxpay)] == TypeWxpay: + return TypeWxpay + default: + return t + } +} + +// CreatePaymentRequest holds the parameters for creating a new payment. +type CreatePaymentRequest struct { + OrderID string // Internal order ID + Amount string // Pay amount in CNY (formatted to 2 decimal places) + PaymentType string // e.g. "alipay", "wxpay", "stripe" + Subject string // Product description + NotifyURL string // Webhook callback URL + ReturnURL string // Browser redirect URL after payment + ClientIP string // Payer's IP address + IsMobile bool // Whether the request comes from a mobile device + InstanceSubMethods string // Comma-separated sub-methods from instance supported_types (for Stripe) +} + +// CreatePaymentResponse is returned after successfully initiating a payment. +type CreatePaymentResponse struct { + TradeNo string // Third-party transaction ID + PayURL string // H5 payment URL (alipay/wxpay) + QRCode string // QR code content for scanning + ClientSecret string // Stripe PaymentIntent client secret +} + +// QueryOrderResponse describes the payment status from the upstream provider. +type QueryOrderResponse struct { + TradeNo string + Status string // "pending", "paid", "failed", "refunded" + Amount float64 // Amount in CNY + PaidAt string // RFC3339 timestamp or empty +} + +// PaymentNotification is the parsed result of a webhook/notify callback. +type PaymentNotification struct { + TradeNo string + OrderID string + Amount float64 + Status string // "success" or "failed" + RawData string // Raw notification body for audit +} + +// RefundRequest contains the parameters for requesting a refund. +type RefundRequest struct { + TradeNo string + OrderID string + Amount string // Refund amount formatted to 2 decimal places + Reason string +} + +// RefundResponse is returned after a refund request. +type RefundResponse struct { + RefundID string + Status string // "success", "pending", "failed" +} + +// InstanceSelection holds the selected provider instance and its decrypted config. +type InstanceSelection struct { + InstanceID string + Config map[string]string + SupportedTypes string // Comma-separated list of supported payment types from the instance + PaymentMode string // Payment display mode: "qrcode", "redirect", "popup" +} + +// Provider defines the interface that all payment providers must implement. +type Provider interface { + // Name returns a human-readable name for this provider. + Name() string + // ProviderKey returns the unique key identifying this provider type (e.g. "easypay"). + ProviderKey() string + // SupportedTypes returns the list of payment types this provider handles. + SupportedTypes() []PaymentType + // CreatePayment initiates a payment and returns the upstream response. + CreatePayment(ctx context.Context, req CreatePaymentRequest) (*CreatePaymentResponse, error) + // QueryOrder queries the payment status of the given trade number. + QueryOrder(ctx context.Context, tradeNo string) (*QueryOrderResponse, error) + // VerifyNotification parses and verifies a webhook callback. + // Returns nil for unrecognized or irrelevant events (caller should return 200). + VerifyNotification(ctx context.Context, rawBody string, headers map[string]string) (*PaymentNotification, error) + // Refund requests a refund from the upstream provider. + Refund(ctx context.Context, req RefundRequest) (*RefundResponse, error) +} + +// CancelableProvider extends Provider with the ability to cancel pending payments. +type CancelableProvider interface { + Provider + // CancelPayment cancels/expires a pending payment on the upstream platform. + CancelPayment(ctx context.Context, tradeNo string) error +} diff --git a/backend/internal/payment/wire.go b/backend/internal/payment/wire.go new file mode 100644 index 0000000000000000000000000000000000000000..9717465d8e87f55ab98b1bd1862e8566c4dd3211 --- /dev/null +++ b/backend/internal/payment/wire.go @@ -0,0 +1,53 @@ +package payment + +import ( + "encoding/hex" + "fmt" + "log/slog" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/google/wire" +) + +// EncryptionKey is a named type for the payment encryption key (AES-256, 32 bytes). +// Using a named type avoids Wire ambiguity with other []byte parameters. +type EncryptionKey []byte + +// ProvideEncryptionKey derives the payment encryption key from the TOTP encryption key in config. +// When the key is empty, nil is returned (payment features that need encryption will be disabled). +// When the key is non-empty but invalid (bad hex or wrong length), an error is returned +// to prevent startup with a misconfigured encryption key. +func ProvideEncryptionKey(cfg *config.Config) (EncryptionKey, error) { + if cfg.Totp.EncryptionKey == "" { + slog.Warn("payment encryption key not configured — encrypted payment config will be unavailable") + return nil, nil + } + key, err := hex.DecodeString(cfg.Totp.EncryptionKey) + if err != nil { + return nil, fmt.Errorf("invalid payment encryption key (hex decode): %w", err) + } + if len(key) != 32 { + return nil, fmt.Errorf("payment encryption key must be 32 bytes, got %d", len(key)) + } + return EncryptionKey(key), nil +} + +// ProvideRegistry creates an empty payment provider registry. +// Providers are registered at runtime after application startup. +func ProvideRegistry() *Registry { + return NewRegistry() +} + +// ProvideDefaultLoadBalancer creates a DefaultLoadBalancer backed by the ent client. +func ProvideDefaultLoadBalancer(client *dbent.Client, key EncryptionKey) *DefaultLoadBalancer { + return NewDefaultLoadBalancer(client, []byte(key)) +} + +// ProviderSet is the Wire provider set for the payment package. +var ProviderSet = wire.NewSet( + ProvideEncryptionKey, + ProvideRegistry, + ProvideDefaultLoadBalancer, + wire.Bind(new(LoadBalancer), new(*DefaultLoadBalancer)), +) diff --git a/backend/internal/pkg/apicompat/types.go b/backend/internal/pkg/apicompat/types.go index b724a5ed96d8001de09384910fa7ee5172330401..b383f8671aa78798cfbdd7cc4e2be6b60da5ba4a 100644 --- a/backend/internal/pkg/apicompat/types.go +++ b/backend/internal/pkg/apicompat/types.go @@ -28,7 +28,7 @@ type AnthropicRequest struct { // AnthropicOutputConfig controls output generation parameters. type AnthropicOutputConfig struct { - Effort string `json:"effort,omitempty"` // "low" | "medium" | "high" + Effort string `json:"effort,omitempty"` // "low" | "medium" | "high" | "max" } // AnthropicThinking configures extended thinking in the Anthropic API. @@ -167,7 +167,7 @@ type ResponsesRequest struct { // ResponsesReasoning configures reasoning effort in the Responses API. type ResponsesReasoning struct { - Effort string `json:"effort"` // "low" | "medium" | "high" + Effort string `json:"effort"` // "low" | "medium" | "high" | "xhigh" Summary string `json:"summary,omitempty"` // "auto" | "concise" | "detailed" } @@ -345,7 +345,7 @@ type ChatCompletionsRequest struct { StreamOptions *ChatStreamOptions `json:"stream_options,omitempty"` Tools []ChatTool `json:"tools,omitempty"` ToolChoice json.RawMessage `json:"tool_choice,omitempty"` - ReasoningEffort string `json:"reasoning_effort,omitempty"` // "low" | "medium" | "high" + ReasoningEffort string `json:"reasoning_effort,omitempty"` // "low" | "medium" | "high" | "xhigh" ServiceTier string `json:"service_tier,omitempty"` Stop json.RawMessage `json:"stop,omitempty"` // string or []string diff --git a/backend/internal/pkg/pagination/pagination.go b/backend/internal/pkg/pagination/pagination.go index c162588ae647d9ab4ce468439ea15c30e9db61b8..ce8e74b8cea4cc9a509a138b2ab395eb1eba14d9 100644 --- a/backend/internal/pkg/pagination/pagination.go +++ b/backend/internal/pkg/pagination/pagination.go @@ -1,10 +1,19 @@ // Package pagination provides types and helpers for paginated responses. package pagination +import "strings" + +const ( + SortOrderAsc = "asc" + SortOrderDesc = "desc" +) + // PaginationParams 分页参数 type PaginationParams struct { - Page int - PageSize int + Page int + PageSize int + SortBy string + SortOrder string } // PaginationResult 分页结果 @@ -18,8 +27,9 @@ type PaginationResult struct { // DefaultPagination 默认分页参数 func DefaultPagination() PaginationParams { return PaginationParams{ - Page: 1, - PageSize: 20, + Page: 1, + PageSize: 20, + SortOrder: SortOrderDesc, } } @@ -36,8 +46,32 @@ func (p PaginationParams) Limit() int { if p.PageSize < 1 { return 20 } - if p.PageSize > 100 { - return 100 + if p.PageSize > 1000 { + return 1000 } return p.PageSize } + +// NormalizeSortOrder normalizes sort order to asc/desc and falls back to defaultOrder. +func NormalizeSortOrder(order string, defaultOrder string) string { + switch strings.ToLower(strings.TrimSpace(defaultOrder)) { + case SortOrderAsc: + defaultOrder = SortOrderAsc + default: + defaultOrder = SortOrderDesc + } + + switch strings.ToLower(strings.TrimSpace(order)) { + case SortOrderAsc: + return SortOrderAsc + case SortOrderDesc: + return SortOrderDesc + default: + return defaultOrder + } +} + +// NormalizedSortOrder returns the normalized sort order using defaultOrder as fallback. +func (p PaginationParams) NormalizedSortOrder(defaultOrder string) string { + return NormalizeSortOrder(p.SortOrder, defaultOrder) +} diff --git a/backend/internal/pkg/pagination/pagination_test.go b/backend/internal/pkg/pagination/pagination_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9a3b069d90940c4173c2ae39088039d4cce8b893 --- /dev/null +++ b/backend/internal/pkg/pagination/pagination_test.go @@ -0,0 +1,71 @@ +package pagination + +import "testing" + +func TestNormalizeSortOrder(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + defaultOrder string + want string + }{ + {name: "asc", input: "asc", defaultOrder: "desc", want: "asc"}, + {name: "uppercase asc", input: "ASC", defaultOrder: "desc", want: "asc"}, + {name: "desc", input: "desc", defaultOrder: "asc", want: "desc"}, + {name: "trim spaces", input: " desc ", defaultOrder: "asc", want: "desc"}, + {name: "invalid falls back", input: "sideways", defaultOrder: "asc", want: "asc"}, + {name: "empty falls back", input: "", defaultOrder: "desc", want: "desc"}, + {name: "invalid default falls back to desc", input: "", defaultOrder: "wat", want: "desc"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := NormalizeSortOrder(tt.input, tt.defaultOrder); got != tt.want { + t.Fatalf("NormalizeSortOrder(%q, %q) = %q, want %q", tt.input, tt.defaultOrder, got, tt.want) + } + }) + } +} + +func TestPaginationParamsNormalizedSortOrder(t *testing.T) { + t.Parallel() + + params := PaginationParams{SortOrder: "ASC"} + if got := params.NormalizedSortOrder("desc"); got != "asc" { + t.Fatalf("NormalizedSortOrder = %q, want asc", got) + } + + params = PaginationParams{SortOrder: "bad"} + if got := params.NormalizedSortOrder("asc"); got != "asc" { + t.Fatalf("NormalizedSortOrder invalid fallback = %q, want asc", got) + } +} + +func TestPaginationParamsLimit(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pageSize int + want int + }{ + {name: "non-positive falls back to default", pageSize: 0, want: 20}, + {name: "negative falls back to default", pageSize: -1, want: 20}, + {name: "normal value keeps", pageSize: 50, want: 50}, + {name: "max value keeps", pageSize: 1000, want: 1000}, + {name: "beyond max clamps to 1000", pageSize: 1500, want: 1000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + p := PaginationParams{PageSize: tt.pageSize} + if got := p.Limit(); got != tt.want { + t.Fatalf("Limit() for PageSize=%d = %d, want %d", tt.pageSize, got, tt.want) + } + }) + } +} diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index 144987154f98f3a73b451ee76cf7c400b4ecb5c9..24115c33d74bbdde478b0d28e9feefb93f9dbe58 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -471,21 +471,58 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati case service.StatusActive: q = q.Where( dbaccount.StatusEQ(status), + dbaccount.SchedulableEQ(true), dbaccount.Or( dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(time.Now()), ), + dbpredicate.Account(func(s *entsql.Selector) { + col := s.C("temp_unschedulable_until") + s.Where(entsql.Or( + entsql.IsNull(col), + entsql.LTE(col, entsql.Expr("NOW()")), + )) + }), ) case "rate_limited": - q = q.Where(dbaccount.RateLimitResetAtGT(time.Now())) + q = q.Where( + dbaccount.StatusEQ(service.StatusActive), + dbaccount.RateLimitResetAtGT(time.Now()), + dbpredicate.Account(func(s *entsql.Selector) { + col := s.C("temp_unschedulable_until") + s.Where(entsql.Or( + entsql.IsNull(col), + entsql.LTE(col, entsql.Expr("NOW()")), + )) + }), + ) case "temp_unschedulable": - q = q.Where(dbpredicate.Account(func(s *entsql.Selector) { - col := s.C("temp_unschedulable_until") - s.Where(entsql.And( - entsql.Not(entsql.IsNull(col)), - entsql.GT(col, entsql.Expr("NOW()")), - )) - })) + q = q.Where( + dbaccount.StatusEQ(service.StatusActive), + dbpredicate.Account(func(s *entsql.Selector) { + col := s.C("temp_unschedulable_until") + s.Where(entsql.And( + entsql.Not(entsql.IsNull(col)), + entsql.GT(col, entsql.Expr("NOW()")), + )) + }), + ) + case "unschedulable": + q = q.Where( + dbaccount.StatusEQ(service.StatusActive), + dbaccount.SchedulableEQ(false), + dbaccount.Or( + dbaccount.RateLimitResetAtIsNil(), + dbaccount.RateLimitResetAtLTE(time.Now()), + ), + dbpredicate.Account(func(s *entsql.Selector) { + col := s.C("temp_unschedulable_until") + s.Where(entsql.Or( + entsql.IsNull(col), + entsql.LTE(col, entsql.Expr("NOW()")), + )) + }), + ) default: q = q.Where(dbaccount.StatusEQ(status)) } @@ -518,11 +555,14 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati return nil, nil, err } - accounts, err := q. + accountsQuery := q. Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(dbaccount.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range accountListOrder(params) { + accountsQuery = accountsQuery.Order(order) + } + + accounts, err := accountsQuery.All(ctx) if err != nil { return nil, nil, err } @@ -534,6 +574,50 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati return outAccounts, paginationResultFromTotal(int64(total), params), nil } +func accountListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderAsc) + + field := dbaccount.FieldName + defaultOrder := true + switch sortBy { + case "", "name": + field = dbaccount.FieldName + case "id": + field = dbaccount.FieldID + defaultOrder = false + case "status": + field = dbaccount.FieldStatus + defaultOrder = false + case "schedulable": + field = dbaccount.FieldSchedulable + defaultOrder = false + case "priority": + field = dbaccount.FieldPriority + defaultOrder = false + case "rate_multiplier": + field = dbaccount.FieldRateMultiplier + defaultOrder = false + case "last_used_at": + field = dbaccount.FieldLastUsedAt + defaultOrder = false + case "expires_at": + field = dbaccount.FieldExpiresAt + defaultOrder = false + case "created_at": + field = dbaccount.FieldCreatedAt + defaultOrder = false + } + + if sortOrder == pagination.SortOrderDesc { + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(dbaccount.FieldID)} + } + if defaultOrder { + return []func(*entsql.Selector){dbent.Asc(dbaccount.FieldName), dbent.Asc(dbaccount.FieldID)} + } + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(dbaccount.FieldID)} +} + func (r *accountRepository) ListByGroup(ctx context.Context, groupID int64) ([]service.Account, error) { accounts, err := r.queryAccountsByGroup(ctx, groupID, accountGroupQueryOptions{ status: service.StatusActive, diff --git a/backend/internal/repository/account_repo_integration_test.go b/backend/internal/repository/account_repo_integration_test.go index f3e3f7452f9eac9d922573acd6e646ec514b8c81..b249bb61b7d30768e0b212b46daa96abae00a07d 100644 --- a/backend/internal/repository/account_repo_integration_test.go +++ b/backend/internal/repository/account_repo_integration_test.go @@ -256,7 +256,7 @@ func (s *AccountRepoSuite) TestListWithFilters() { }, }, { - name: "filter_by_status_active_excludes_rate_limited", + name: "filter_by_status_active_excludes_runtime_blocked_accounts", setup: func(client *dbent.Client) { mustCreateAccount(s.T(), client, &service.Account{Name: "active-normal", Status: service.StatusActive}) rateLimited := mustCreateAccount(s.T(), client, &service.Account{Name: "active-rate-limited", Status: service.StatusActive}) @@ -264,6 +264,16 @@ func (s *AccountRepoSuite) TestListWithFilters() { SetRateLimitResetAt(time.Now().Add(10 * time.Minute)). Exec(context.Background()) s.Require().NoError(err) + tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive}) + err = client.Account.UpdateOneID(tempUnsched.ID). + SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)). + Exec(context.Background()) + s.Require().NoError(err) + unsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-unsched", Status: service.StatusActive}) + err = client.Account.UpdateOneID(unsched.ID). + SetSchedulable(false). + Exec(context.Background()) + s.Require().NoError(err) }, status: service.StatusActive, wantCount: 1, @@ -271,6 +281,75 @@ func (s *AccountRepoSuite) TestListWithFilters() { s.Require().Equal("active-normal", accounts[0].Name) }, }, + { + name: "filter_by_status_unschedulable_excludes_rate_limited_and_temp_unschedulable", + setup: func(client *dbent.Client) { + mustCreateAccount(s.T(), client, &service.Account{Name: "active-normal", Status: service.StatusActive, Schedulable: true}) + unsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-unsched", Status: service.StatusActive}) + err := client.Account.UpdateOneID(unsched.ID). + SetSchedulable(false). + Exec(context.Background()) + s.Require().NoError(err) + rateLimited := mustCreateAccount(s.T(), client, &service.Account{Name: "active-rate-limited", Status: service.StatusActive}) + err = client.Account.UpdateOneID(rateLimited.ID). + SetSchedulable(false). + SetRateLimitResetAt(time.Now().Add(10 * time.Minute)). + Exec(context.Background()) + s.Require().NoError(err) + tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive}) + err = client.Account.UpdateOneID(tempUnsched.ID). + SetSchedulable(false). + SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)). + Exec(context.Background()) + s.Require().NoError(err) + }, + status: "unschedulable", + wantCount: 1, + validate: func(accounts []service.Account) { + s.Require().Equal("active-unsched", accounts[0].Name) + }, + }, + { + name: "filter_by_status_rate_limited_excludes_temp_unschedulable", + setup: func(client *dbent.Client) { + rateLimited := mustCreateAccount(s.T(), client, &service.Account{Name: "active-rate-limited", Status: service.StatusActive}) + err := client.Account.UpdateOneID(rateLimited.ID). + SetRateLimitResetAt(time.Now().Add(10 * time.Minute)). + Exec(context.Background()) + s.Require().NoError(err) + tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive}) + err = client.Account.UpdateOneID(tempUnsched.ID). + SetRateLimitResetAt(time.Now().Add(20 * time.Minute)). + SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)). + Exec(context.Background()) + s.Require().NoError(err) + }, + status: "rate_limited", + wantCount: 1, + validate: func(accounts []service.Account) { + s.Require().Equal("active-rate-limited", accounts[0].Name) + }, + }, + { + name: "filter_by_status_temp_unschedulable_excludes_manually_unschedulable", + setup: func(client *dbent.Client) { + tempUnsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-temp-unsched", Status: service.StatusActive, Schedulable: true}) + err := client.Account.UpdateOneID(tempUnsched.ID). + SetTempUnschedulableUntil(time.Now().Add(15 * time.Minute)). + Exec(context.Background()) + s.Require().NoError(err) + unsched := mustCreateAccount(s.T(), client, &service.Account{Name: "active-unsched", Status: service.StatusActive}) + err = client.Account.UpdateOneID(unsched.ID). + SetSchedulable(false). + Exec(context.Background()) + s.Require().NoError(err) + }, + status: "temp_unschedulable", + wantCount: 1, + validate: func(accounts []service.Account) { + s.Require().Equal("active-temp-unsched", accounts[0].Name) + }, + }, { name: "filter_by_search", setup: func(client *dbent.Client) { diff --git a/backend/internal/repository/account_repo_sort_integration_test.go b/backend/internal/repository/account_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..098dde7bbdb5a05a4c4c58546133cebb7485e002 --- /dev/null +++ b/backend/internal/repository/account_repo_sort_integration_test.go @@ -0,0 +1,35 @@ +//go:build integration + +package repository + +import ( + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func (s *AccountRepoSuite) TestList_DefaultSortByNameAsc() { + mustCreateAccount(s.T(), s.client, &service.Account{Name: "z-account"}) + mustCreateAccount(s.T(), s.client, &service.Account{Name: "a-account"}) + + accounts, _, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}) + s.Require().NoError(err) + s.Require().Len(accounts, 2) + s.Require().Equal("a-account", accounts[0].Name) + s.Require().Equal("z-account", accounts[1].Name) +} + +func (s *AccountRepoSuite) TestListWithFilters_SortByPriorityDesc() { + mustCreateAccount(s.T(), s.client, &service.Account{Name: "low-priority", Priority: 10}) + mustCreateAccount(s.T(), s.client, &service.Account{Name: "high-priority", Priority: 90}) + + accounts, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "priority", + SortOrder: "desc", + }, "", "", "", "", 0, "") + s.Require().NoError(err) + s.Require().Len(accounts, 2) + s.Require().Equal("high-priority", accounts[0].Name) + s.Require().Equal("low-priority", accounts[1].Name) +} diff --git a/backend/internal/repository/announcement_repo.go b/backend/internal/repository/announcement_repo.go index 53dc335f8c867f83b4f9141c6a11e51f9b2b19bf..afe1fb25c9000ca31dcb6e6e72caa35c02d5a8f1 100644 --- a/backend/internal/repository/announcement_repo.go +++ b/backend/internal/repository/announcement_repo.go @@ -2,12 +2,15 @@ package repository import ( "context" + "strings" "time" dbent "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/ent/announcement" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/Wei-Shaw/sub2api/internal/service" + + entsql "entgo.io/ent/dialect/sql" ) type announcementRepository struct { @@ -128,11 +131,14 @@ func (r *announcementRepository) List( return nil, nil, err } - items, err := q. + itemsQuery := q. Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(announcement.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range announcementListOrders(params) { + itemsQuery = itemsQuery.Order(order) + } + + items, err := itemsQuery.All(ctx) if err != nil { return nil, nil, err } @@ -141,6 +147,56 @@ func (r *announcementRepository) List( return out, paginationResultFromTotal(int64(total), params), nil } +func announcementListOrder(params pagination.PaginationParams) (string, string) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + + switch sortBy { + case "title": + return announcement.FieldTitle, sortOrder + case "status": + return announcement.FieldStatus, sortOrder + case "notify_mode": + return announcement.FieldNotifyMode, sortOrder + case "starts_at": + return announcement.FieldStartsAt, sortOrder + case "ends_at": + return announcement.FieldEndsAt, sortOrder + case "id": + return announcement.FieldID, sortOrder + case "", "created_at": + return announcement.FieldCreatedAt, sortOrder + default: + return announcement.FieldCreatedAt, pagination.SortOrderDesc + } +} + +func announcementListOrders(params pagination.PaginationParams) []func(*entsql.Selector) { + field, sortOrder := announcementListOrder(params) + + if sortOrder == pagination.SortOrderAsc { + if field == announcement.FieldID { + return []func(*entsql.Selector){ + dbent.Asc(field), + } + } + return []func(*entsql.Selector){ + dbent.Asc(field), + dbent.Asc(announcement.FieldID), + } + } + + if field == announcement.FieldID { + return []func(*entsql.Selector){ + dbent.Desc(field), + } + } + return []func(*entsql.Selector){ + dbent.Desc(field), + dbent.Desc(announcement.FieldID), + } +} + func (r *announcementRepository) ListActive(ctx context.Context, now time.Time) ([]service.Announcement, error) { q := r.client.Announcement.Query(). Where( diff --git a/backend/internal/repository/announcement_repo_sort_test.go b/backend/internal/repository/announcement_repo_sort_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e47f98dcfc5e3c5c53eaa7c32ad2211d74cc4286 --- /dev/null +++ b/backend/internal/repository/announcement_repo_sort_test.go @@ -0,0 +1,63 @@ +package repository + +import ( + "testing" + + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" +) + +func TestAnnouncementListOrder(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + params pagination.PaginationParams + wantBy string + want string + }{ + { + name: "default created_at desc", + params: pagination.PaginationParams{}, + wantBy: "created_at", + want: "desc", + }, + { + name: "title asc", + params: pagination.PaginationParams{ + SortBy: "title", + SortOrder: "ASC", + }, + wantBy: "title", + want: "asc", + }, + { + name: "status desc", + params: pagination.PaginationParams{ + SortBy: "status", + SortOrder: "desc", + }, + wantBy: "status", + want: "desc", + }, + { + name: "invalid falls back", + params: pagination.PaginationParams{ + SortBy: "sideways", + SortOrder: "wat", + }, + wantBy: "created_at", + want: "desc", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + gotBy, gotOrder := announcementListOrder(tt.params) + if gotBy != tt.wantBy || gotOrder != tt.want { + t.Fatalf("announcementListOrder(%+v) = (%q, %q), want (%q, %q)", tt.params, gotBy, gotOrder, tt.wantBy, tt.want) + } + }) + } +} diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go index b3b12e8113d2e3e80b49cf4cd90dacc7fca3d5cb..7fd988550f36625cefd9c3ae51a7518fd3852ae5 100644 --- a/backend/internal/repository/api_key_repo.go +++ b/backend/internal/repository/api_key_repo.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "strings" "time" dbent "github.com/Wei-Shaw/sub2api/ent" @@ -14,6 +15,8 @@ import ( "github.com/Wei-Shaw/sub2api/internal/service" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + + entsql "entgo.io/ent/dialect/sql" ) type apiKeyRepository struct { @@ -164,6 +167,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se group.FieldSupportedModelScopes, group.FieldAllowMessagesDispatch, group.FieldDefaultMappedModel, + group.FieldMessagesDispatchModelConfig, ) }). Only(ctx) @@ -309,12 +313,15 @@ func (r *apiKeyRepository) ListByUserID(ctx context.Context, userID int64, param return nil, nil, err } - keys, err := q. + keysQuery := q. WithGroup(). Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(apikey.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range apiKeyListOrder(params) { + keysQuery = keysQuery.Order(order) + } + + keys, err := keysQuery.All(ctx) if err != nil { return nil, nil, err } @@ -359,12 +366,15 @@ func (r *apiKeyRepository) ListByGroupID(ctx context.Context, groupID int64, par return nil, nil, err } - keys, err := q. + keysQuery := q. WithUser(). Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(apikey.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range apiKeyListOrder(params) { + keysQuery = keysQuery.Order(order) + } + + keys, err := keysQuery.All(ctx) if err != nil { return nil, nil, err } @@ -377,6 +387,32 @@ func (r *apiKeyRepository) ListByGroupID(ctx context.Context, groupID int64, par return outKeys, paginationResultFromTotal(int64(total), params), nil } +func apiKeyListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + + var field string + switch sortBy { + case "name": + field = apikey.FieldName + case "status": + field = apikey.FieldStatus + case "expires_at": + field = apikey.FieldExpiresAt + case "last_used_at": + field = apikey.FieldLastUsedAt + case "created_at": + field = apikey.FieldCreatedAt + default: + field = apikey.FieldID + } + + if sortOrder == pagination.SortOrderAsc { + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(apikey.FieldID)} + } + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(apikey.FieldID)} +} + // SearchAPIKeys searches API keys by user ID and/or keyword (name) func (r *apiKeyRepository) SearchAPIKeys(ctx context.Context, userID int64, keyword string, limit int) ([]service.APIKey, error) { q := r.activeQuery() @@ -654,6 +690,7 @@ func groupEntityToService(g *dbent.Group) *service.Group { RequireOAuthOnly: g.RequireOauthOnly, RequirePrivacySet: g.RequirePrivacySet, DefaultMappedModel: g.DefaultMappedModel, + MessagesDispatchModelConfig: g.MessagesDispatchModelConfig, CreatedAt: g.CreatedAt, UpdatedAt: g.UpdatedAt, } diff --git a/backend/internal/repository/api_key_repo_integration_test.go b/backend/internal/repository/api_key_repo_integration_test.go index 7d5c18260ba25bc5899ec24d72db18e313e522d4..e926ed8602843311d001d37c8a8bc3b5c4233bb5 100644 --- a/backend/internal/repository/api_key_repo_integration_test.go +++ b/backend/internal/repository/api_key_repo_integration_test.go @@ -86,6 +86,45 @@ func (s *APIKeyRepoSuite) TestGetByKey_NotFound() { s.Require().Error(err, "expected error for non-existent key") } +func (s *APIKeyRepoSuite) TestGetByKeyForAuth_PreservesMessagesDispatchModelConfig() { + user := s.mustCreateUser("getbykey-auth-dispatch@test.com") + group, err := s.client.Group.Create(). + SetName("g-auth-dispatch"). + SetPlatform(service.PlatformOpenAI). + SetStatus(service.StatusActive). + SetSubscriptionType(service.SubscriptionTypeStandard). + SetRateMultiplier(1). + SetAllowMessagesDispatch(true). + SetDefaultMappedModel("gpt-5.4"). + SetMessagesDispatchModelConfig(service.OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4-nano", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: "gpt-5.4-mini", + ExactModelMappings: map[string]string{ + "claude-sonnet-4.5": "gpt-5.4-nano", + }, + }). + Save(s.ctx) + s.Require().NoError(err) + + key := &service.APIKey{ + UserID: user.ID, + Key: "sk-getbykey-auth-dispatch", + Name: "Dispatch Key", + GroupID: &group.ID, + Status: service.StatusActive, + } + s.Require().NoError(s.repo.Create(s.ctx, key)) + + got, err := s.repo.GetByKeyForAuth(s.ctx, key.Key) + s.Require().NoError(err) + s.Require().NotNil(got.Group) + s.Require().True(got.Group.AllowMessagesDispatch) + s.Require().Equal("gpt-5.4", got.Group.DefaultMappedModel) + s.Require().Equal("gpt-5.4-nano", got.Group.MessagesDispatchModelConfig.OpusMappedModel) + s.Require().Equal("gpt-5.4-nano", got.Group.MessagesDispatchModelConfig.ExactModelMappings["claude-sonnet-4.5"]) +} + // --- Update --- func (s *APIKeyRepoSuite) TestUpdate() { diff --git a/backend/internal/repository/api_key_repo_messages_dispatch_unit_test.go b/backend/internal/repository/api_key_repo_messages_dispatch_unit_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aba62ead2ebf7639ca5cb3788c4b1256feb6927e --- /dev/null +++ b/backend/internal/repository/api_key_repo_messages_dispatch_unit_test.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + "testing" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/stretchr/testify/require" +) + +func TestGroupEntityToService_PreservesMessagesDispatchModelConfig(t *testing.T) { + group := &dbent.Group{ + ID: 1, + Name: "openai-dispatch", + Platform: service.PlatformOpenAI, + Status: service.StatusActive, + SubscriptionType: service.SubscriptionTypeStandard, + RateMultiplier: 1, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + MessagesDispatchModelConfig: service.OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4-nano", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: "gpt-5.4-mini", + ExactModelMappings: map[string]string{ + "claude-sonnet-4.5": "gpt-5.4-nano", + }, + }, + } + + got := groupEntityToService(group) + require.NotNil(t, got) + require.Equal(t, group.MessagesDispatchModelConfig, got.MessagesDispatchModelConfig) +} + +func TestAPIKeyRepository_GetByKeyForAuth_PreservesMessagesDispatchModelConfig_SQLite(t *testing.T) { + repo, client := newAPIKeyRepoSQLite(t) + ctx := context.Background() + user := mustCreateAPIKeyRepoUser(t, ctx, client, "getbykey-auth-dispatch-unit@test.com") + + group, err := client.Group.Create(). + SetName("g-auth-dispatch-unit"). + SetPlatform(service.PlatformOpenAI). + SetStatus(service.StatusActive). + SetSubscriptionType(service.SubscriptionTypeStandard). + SetRateMultiplier(1). + SetAllowMessagesDispatch(true). + SetDefaultMappedModel("gpt-5.4"). + SetMessagesDispatchModelConfig(service.OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4-nano", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: "gpt-5.4-mini", + ExactModelMappings: map[string]string{ + "claude-sonnet-4.5": "gpt-5.4-nano", + }, + }). + Save(ctx) + require.NoError(t, err) + + key := &service.APIKey{ + UserID: user.ID, + Key: "sk-getbykey-auth-dispatch-unit", + Name: "Dispatch Key Unit", + GroupID: &group.ID, + Status: service.StatusActive, + } + require.NoError(t, repo.Create(ctx, key)) + + got, err := repo.GetByKeyForAuth(ctx, key.Key) + require.NoError(t, err) + require.NotNil(t, got.Group) + require.Equal(t, group.MessagesDispatchModelConfig, got.Group.MessagesDispatchModelConfig) +} diff --git a/backend/internal/repository/api_key_repo_sort_integration_test.go b/backend/internal/repository/api_key_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..69812882feb5f577b07ad1f0f56f9ac62c6b7789 --- /dev/null +++ b/backend/internal/repository/api_key_repo_sort_integration_test.go @@ -0,0 +1,25 @@ +//go:build integration + +package repository + +import ( + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func (s *APIKeyRepoSuite) TestListByUserID_SortByNameAsc() { + user := s.mustCreateUser("sort-name@example.com") + s.mustCreateApiKey(user.ID, "sk-z", "z-key", nil) + s.mustCreateApiKey(user.ID, "sk-a", "a-key", nil) + + keys, _, err := s.repo.ListByUserID(s.ctx, user.ID, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "name", + SortOrder: "asc", + }, service.APIKeyListFilters{}) + s.Require().NoError(err) + s.Require().Len(keys, 2) + s.Require().Equal("a-key", keys[0].Name) + s.Require().Equal("z-key", keys[1].Name) +} diff --git a/backend/internal/repository/channel_repo.go b/backend/internal/repository/channel_repo.go index 1e2c2e4cf34b93b735aac6eea810a0c70995f4ed..49c2d8d92c349496686fdee208c87b0eb87d2fc6 100644 --- a/backend/internal/repository/channel_repo.go +++ b/backend/internal/repository/channel_repo.go @@ -188,8 +188,8 @@ func (r *channelRepository) List(ctx context.Context, params pagination.Paginati // 查询 channel 列表 dataQuery := fmt.Sprintf( `SELECT c.id, c.name, c.description, c.status, c.model_mapping, c.billing_model_source, c.restrict_models, c.created_at, c.updated_at - FROM channels c WHERE %s ORDER BY c.id ASC LIMIT $%d OFFSET $%d`, - whereClause, argIdx, argIdx+1, + FROM channels c WHERE %s ORDER BY %s LIMIT $%d OFFSET $%d`, + whereClause, channelListOrderBy(params), argIdx, argIdx+1, ) args = append(args, pageSize, offset) @@ -246,6 +246,31 @@ func (r *channelRepository) List(ctx context.Context, params pagination.Paginati return channels, paginationResult, nil } +func channelListOrderBy(params pagination.PaginationParams) string { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := strings.ToUpper(params.NormalizedSortOrder(pagination.SortOrderAsc)) + + var column string + switch sortBy { + case "": + column = "c.id" + sortOrder = "ASC" + case "id": + column = "c.id" + case "name": + column = "c.name" + case "status": + column = "c.status" + case "created_at": + column = "c.created_at" + default: + column = "c.id" + sortOrder = "ASC" + } + + return fmt.Sprintf("%s %s, c.id %s", column, sortOrder, sortOrder) +} + func (r *channelRepository) ListAll(ctx context.Context) ([]service.Channel, error) { rows, err := r.db.QueryContext(ctx, `SELECT id, name, description, status, model_mapping, billing_model_source, restrict_models, created_at, updated_at FROM channels ORDER BY id`, diff --git a/backend/internal/repository/channel_repo_test.go b/backend/internal/repository/channel_repo_test.go index 5a59948d25c43fd76fe40c76fec9f3e4692810f6..e761866d709ac0de690615137c1ae3ba7aeee1b1 100644 --- a/backend/internal/repository/channel_repo_test.go +++ b/backend/internal/repository/channel_repo_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/lib/pq" "github.com/stretchr/testify/require" ) @@ -225,3 +226,12 @@ func TestIsUniqueViolation(t *testing.T) { }) } } + +func TestChannelListOrderBy_AllowsDescendingIDSort(t *testing.T) { + params := pagination.PaginationParams{ + SortBy: "id", + SortOrder: "desc", + } + + require.Equal(t, "c.id DESC, c.id DESC", channelListOrderBy(params)) +} diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go index a075b586c49ace4762ac08ce69b433ec71bf4200..c17e3365d3ce70e0b0b1482330bc9083a3590eae 100644 --- a/backend/internal/repository/group_repo.go +++ b/backend/internal/repository/group_repo.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "sort" "strings" dbent "github.com/Wei-Shaw/sub2api/ent" @@ -14,6 +15,8 @@ import ( "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/lib/pq" + + entsql "entgo.io/ent/dialect/sql" ) type sqlExecutor interface { @@ -40,6 +43,7 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er SetDescription(groupIn.Description). SetPlatform(groupIn.Platform). SetRateMultiplier(groupIn.RateMultiplier). + SetSortOrder(groupIn.SortOrder). SetIsExclusive(groupIn.IsExclusive). SetStatus(groupIn.Status). SetSubscriptionType(groupIn.SubscriptionType). @@ -58,7 +62,8 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch). SetRequireOauthOnly(groupIn.RequireOAuthOnly). SetRequirePrivacySet(groupIn.RequirePrivacySet). - SetDefaultMappedModel(groupIn.DefaultMappedModel) + SetDefaultMappedModel(groupIn.DefaultMappedModel). + SetMessagesDispatchModelConfig(groupIn.MessagesDispatchModelConfig) // 设置模型路由配置 if groupIn.ModelRouting != nil { @@ -124,7 +129,8 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch). SetRequireOauthOnly(groupIn.RequireOAuthOnly). SetRequirePrivacySet(groupIn.RequirePrivacySet). - SetDefaultMappedModel(groupIn.DefaultMappedModel) + SetDefaultMappedModel(groupIn.DefaultMappedModel). + SetMessagesDispatchModelConfig(groupIn.MessagesDispatchModelConfig) // 显式处理可空字段:nil 需要 clear,非 nil 需要 set。 if groupIn.DailyLimitUSD != nil { @@ -231,11 +237,18 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination return nil, nil, err } - groups, err := q. + if strings.EqualFold(strings.TrimSpace(params.SortBy), "account_count") { + return r.listWithAccountCountSort(ctx, q, params, total) + } + + groupsQuery := q. Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range groupListOrder(params) { + groupsQuery = groupsQuery.Order(order) + } + + groups, err := groupsQuery.All(ctx) if err != nil { return nil, nil, err } @@ -261,6 +274,104 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination return outGroups, paginationResultFromTotal(int64(total), params), nil } +func (r *groupRepository) listWithAccountCountSort(ctx context.Context, q *dbent.GroupQuery, params pagination.PaginationParams, total int) ([]service.Group, *pagination.PaginationResult, error) { + groups, err := q. + Order(dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)). + All(ctx) + if err != nil { + return nil, nil, err + } + + groupIDs := make([]int64, 0, len(groups)) + outGroups := make([]service.Group, 0, len(groups)) + for i := range groups { + g := groupEntityToService(groups[i]) + outGroups = append(outGroups, *g) + groupIDs = append(groupIDs, g.ID) + } + + counts, err := r.loadAccountCounts(ctx, groupIDs) + if err != nil { + return nil, nil, err + } + for i := range outGroups { + c := counts[outGroups[i].ID] + outGroups[i].AccountCount = c.Total + outGroups[i].ActiveAccountCount = c.Active + outGroups[i].RateLimitedAccountCount = c.RateLimited + } + + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + sort.SliceStable(outGroups, func(i, j int) bool { + if outGroups[i].AccountCount == outGroups[j].AccountCount { + if outGroups[i].SortOrder == outGroups[j].SortOrder { + return outGroups[i].ID < outGroups[j].ID + } + return outGroups[i].SortOrder < outGroups[j].SortOrder + } + if sortOrder == pagination.SortOrderAsc { + return outGroups[i].AccountCount < outGroups[j].AccountCount + } + return outGroups[i].AccountCount > outGroups[j].AccountCount + }) + + return paginateSlice(outGroups, params), paginationResultFromTotal(int64(total), params), nil +} + +func groupListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderAsc) + + var field string + tieField := group.FieldID + defaultOrder := true + switch sortBy { + case "", "sort_order": + field = group.FieldSortOrder + case "name": + field = group.FieldName + defaultOrder = false + case "platform": + field = group.FieldPlatform + defaultOrder = false + case "billing_type", "subscription_type": + field = group.FieldSubscriptionType + defaultOrder = false + case "rate_multiplier": + field = group.FieldRateMultiplier + defaultOrder = false + case "is_exclusive": + field = group.FieldIsExclusive + defaultOrder = false + case "status": + field = group.FieldStatus + defaultOrder = false + case "created_at": + field = group.FieldCreatedAt + defaultOrder = false + case "id": + field = group.FieldID + defaultOrder = false + tieField = "" + default: + field = group.FieldSortOrder + } + + if sortOrder == pagination.SortOrderDesc && sortBy != "" { + if tieField == "" { + return []func(*entsql.Selector){dbent.Desc(field)} + } + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(tieField)} + } + if defaultOrder { + return []func(*entsql.Selector){dbent.Asc(group.FieldSortOrder), dbent.Asc(group.FieldID)} + } + if tieField == "" { + return []func(*entsql.Selector){dbent.Asc(field)} + } + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(tieField)} +} + func (r *groupRepository) ListActive(ctx context.Context) ([]service.Group, error) { groups, err := r.client.Group.Query(). Where(group.StatusEQ(service.StatusActive)). diff --git a/backend/internal/repository/group_repo_integration_test.go b/backend/internal/repository/group_repo_integration_test.go index eccf5ceacbd692e760c53f34fa119b9c3370ad72..f91dae43eb6d9bf8eea97c785e1e9f1d4be54445 100644 --- a/backend/internal/repository/group_repo_integration_test.go +++ b/backend/internal/repository/group_repo_integration_test.go @@ -113,6 +113,33 @@ func (s *GroupRepoSuite) TestUpdate() { s.Require().Equal("updated", got.Name) } +func (s *GroupRepoSuite) TestGetByID_PreservesMessagesDispatchModelConfig() { + group := &service.Group{ + Name: "openai-dispatch", + Platform: service.PlatformOpenAI, + RateMultiplier: 1.0, + IsExclusive: false, + Status: service.StatusActive, + SubscriptionType: service.SubscriptionTypeStandard, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + MessagesDispatchModelConfig: service.OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: "gpt-5.4-mini", + ExactModelMappings: map[string]string{ + "claude-sonnet-4.5": "gpt-5.4-nano", + }, + }, + } + + s.Require().NoError(s.repo.Create(s.ctx, group)) + + got, err := s.repo.GetByID(s.ctx, group.ID) + s.Require().NoError(err) + s.Require().Equal(group.MessagesDispatchModelConfig, got.MessagesDispatchModelConfig) +} + func (s *GroupRepoSuite) TestDelete() { group := &service.Group{ Name: "to-delete", diff --git a/backend/internal/repository/group_repo_sort_integration_test.go b/backend/internal/repository/group_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..85b2efcc61c0f6893d7034e3e8ed94845bae67c9 --- /dev/null +++ b/backend/internal/repository/group_repo_sort_integration_test.go @@ -0,0 +1,50 @@ +//go:build integration + +package repository + +import ( + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func (s *GroupRepoSuite) TestList_DefaultSortBySortOrderAsc() { + g1 := &service.Group{Name: "g1", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 20} + g2 := &service.Group{Name: "g2", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 10} + s.Require().NoError(s.repo.Create(s.ctx, g1)) + s.Require().NoError(s.repo.Create(s.ctx, g2)) + + groups, _, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 100}) + s.Require().NoError(err) + s.Require().GreaterOrEqual(len(groups), 2) + indexByID := make(map[int64]int, len(groups)) + for i, g := range groups { + indexByID[g.ID] = i + } + s.Require().Contains(indexByID, g1.ID) + s.Require().Contains(indexByID, g2.ID) + // g2 has SortOrder=10, g1 has SortOrder=20; ascending means g2 comes first + s.Require().Less(indexByID[g2.ID], indexByID[g1.ID]) +} + +func (s *GroupRepoSuite) TestList_SortBySortOrderDesc() { + g1 := &service.Group{Name: "g1", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 40} + g2 := &service.Group{Name: "g2", Platform: service.PlatformAnthropic, RateMultiplier: 1, Status: service.StatusActive, SubscriptionType: service.SubscriptionTypeStandard, SortOrder: 50} + s.Require().NoError(s.repo.Create(s.ctx, g1)) + s.Require().NoError(s.repo.Create(s.ctx, g2)) + + groups, _, err := s.repo.List(s.ctx, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "sort_order", + SortOrder: "desc", + }) + s.Require().NoError(err) + s.Require().GreaterOrEqual(len(groups), 2) + indexByID := make(map[int64]int, len(groups)) + for i, group := range groups { + indexByID[group.ID] = i + } + s.Require().Contains(indexByID, g1.ID) + s.Require().Contains(indexByID, g2.ID) + s.Require().Less(indexByID[g2.ID], indexByID[g1.ID]) +} diff --git a/backend/internal/repository/integration_harness_test.go b/backend/internal/repository/integration_harness_test.go index fb9c26c4aabedb3bc08070108880dd8b87fe4bcf..5857fbcb2ac1236c83d50293dbb6f6574e9f7f81 100644 --- a/backend/internal/repository/integration_harness_test.go +++ b/backend/internal/repository/integration_harness_test.go @@ -332,6 +332,10 @@ func (h prefixHook) prefixCmd(cmd redisclient.Cmder) { "hgetall", "hget", "hset", "hdel", "hincrbyfloat", "exists", "zadd", "zcard", "zrange", "zrangebyscore", "zrem", "zremrangebyscore", "zrevrange", "zrevrangebyscore", "zscore": prefixOne(1) + case "mget": + for i := 1; i < len(args); i++ { + prefixOne(i) + } case "del", "unlink": for i := 1; i < len(args); i++ { prefixOne(i) diff --git a/backend/internal/repository/pagination.go b/backend/internal/repository/pagination.go index ff08c34be1e45d327cb6d20af3ab968cf7630b89..87c42a59818da658d4348f8ff4ab2b7e75c40faa 100644 --- a/backend/internal/repository/pagination.go +++ b/backend/internal/repository/pagination.go @@ -14,3 +14,22 @@ func paginationResultFromTotal(total int64, params pagination.PaginationParams) Pages: pages, } } + +func paginateSlice[T any](items []T, params pagination.PaginationParams) []T { + if len(items) == 0 { + return []T{} + } + + offset := params.Offset() + if offset >= len(items) { + return []T{} + } + + limit := params.Limit() + end := offset + limit + if end > len(items) { + end = len(items) + } + + return items[offset:end] +} diff --git a/backend/internal/repository/promo_code_repo.go b/backend/internal/repository/promo_code_repo.go index 95ce687a2a323345d48617c113cba1f31f232cbc..d9c76bb3d49611acba0fe1f1c5c572dac0ef6821 100644 --- a/backend/internal/repository/promo_code_repo.go +++ b/backend/internal/repository/promo_code_repo.go @@ -2,12 +2,15 @@ package repository import ( "context" + "strings" 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" + + entsql "entgo.io/ent/dialect/sql" ) type promoCodeRepository struct { @@ -137,11 +140,14 @@ func (r *promoCodeRepository) ListWithFilters(ctx context.Context, params pagina return nil, nil, err } - codes, err := q. + codesQuery := q. Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(promocode.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range promoCodeListOrder(params) { + codesQuery = codesQuery.Order(order) + } + + codes, err := codesQuery.All(ctx) if err != nil { return nil, nil, err } @@ -151,6 +157,32 @@ func (r *promoCodeRepository) ListWithFilters(ctx context.Context, params pagina return outCodes, paginationResultFromTotal(int64(total), params), nil } +func promoCodeListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + + var field string + switch sortBy { + case "bonus_amount": + field = promocode.FieldBonusAmount + case "status": + field = promocode.FieldStatus + case "expires_at": + field = promocode.FieldExpiresAt + case "created_at": + field = promocode.FieldCreatedAt + case "code": + field = promocode.FieldCode + default: + field = promocode.FieldID + } + + if sortOrder == pagination.SortOrderAsc { + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(promocode.FieldID)} + } + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(promocode.FieldID)} +} + func (r *promoCodeRepository) CreateUsage(ctx context.Context, usage *service.PromoCodeUsage) error { client := clientFromContext(ctx, r.client) created, err := client.PromoCodeUsage.Create(). diff --git a/backend/internal/repository/proxy_repo.go b/backend/internal/repository/proxy_repo.go index 07c2a2049844c3f1153be2eacb5e0a44476275ef..60b2f069ecb52c764a63ed81751bb506d788459a 100644 --- a/backend/internal/repository/proxy_repo.go +++ b/backend/internal/repository/proxy_repo.go @@ -3,12 +3,16 @@ package repository import ( "context" "database/sql" + "sort" + "strings" dbent "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/ent/proxy" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + + entsql "entgo.io/ent/dialect/sql" ) type sqlQuerier interface { @@ -135,11 +139,14 @@ func (r *proxyRepository) ListWithFilters(ctx context.Context, params pagination return nil, nil, err } - proxies, err := q. + proxiesQuery := q. Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(proxy.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range proxyListOrder(params) { + proxiesQuery = proxiesQuery.Order(order) + } + + proxies, err := proxiesQuery.All(ctx) if err != nil { return nil, nil, err } @@ -170,22 +177,58 @@ func (r *proxyRepository) ListWithFiltersAndAccountCount(ctx context.Context, pa return nil, nil, err } - proxies, err := q. + if strings.EqualFold(strings.TrimSpace(params.SortBy), "account_count") { + return r.listWithAccountCountSort(ctx, q, params, total) + } + + proxiesQuery := q. Offset(params.Offset()). - Limit(params.Limit()). + Limit(params.Limit()) + for _, order := range proxyListOrder(params) { + proxiesQuery = proxiesQuery.Order(order) + } + + proxies, err := proxiesQuery.All(ctx) + if err != nil { + return nil, nil, err + } + + return r.buildProxyWithAccountCountResult(ctx, proxies, params, int64(total)) +} + +func (r *proxyRepository) listWithAccountCountSort(ctx context.Context, q *dbent.ProxyQuery, params pagination.PaginationParams, total int) ([]service.ProxyWithAccountCount, *pagination.PaginationResult, error) { + proxies, err := q. Order(dbent.Desc(proxy.FieldID)). All(ctx) if err != nil { return nil, nil, err } - // Get account counts + result, _, err := r.buildProxyWithAccountCountResult(ctx, proxies, params, int64(total)) + if err != nil { + return nil, nil, err + } + + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + sort.SliceStable(result, func(i, j int) bool { + if result[i].AccountCount == result[j].AccountCount { + return result[i].ID > result[j].ID + } + if sortOrder == pagination.SortOrderAsc { + return result[i].AccountCount < result[j].AccountCount + } + return result[i].AccountCount > result[j].AccountCount + }) + + return paginateSlice(result, params), paginationResultFromTotal(int64(total), params), nil +} + +func (r *proxyRepository) buildProxyWithAccountCountResult(ctx context.Context, proxies []*dbent.Proxy, params pagination.PaginationParams, total int64) ([]service.ProxyWithAccountCount, *pagination.PaginationResult, error) { counts, err := r.GetAccountCountsForProxies(ctx) if err != nil { return nil, nil, err } - // Build result with account counts result := make([]service.ProxyWithAccountCount, 0, len(proxies)) for i := range proxies { proxyOut := proxyEntityToService(proxies[i]) @@ -198,7 +241,31 @@ func (r *proxyRepository) ListWithFiltersAndAccountCount(ctx context.Context, pa }) } - return result, paginationResultFromTotal(int64(total), params), nil + return result, paginationResultFromTotal(total, params), nil +} + +func proxyListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + + var field string + switch sortBy { + case "name": + field = proxy.FieldName + case "protocol": + field = proxy.FieldProtocol + case "status": + field = proxy.FieldStatus + case "created_at": + field = proxy.FieldCreatedAt + default: + field = proxy.FieldID + } + + if sortOrder == pagination.SortOrderAsc { + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(proxy.FieldID)} + } + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(proxy.FieldID)} } func (r *proxyRepository) ListActive(ctx context.Context) ([]service.Proxy, error) { diff --git a/backend/internal/repository/proxy_repo_sort_integration_test.go b/backend/internal/repository/proxy_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fe1c2873ba2003c5e465f9665d72825bc14011d9 --- /dev/null +++ b/backend/internal/repository/proxy_repo_sort_integration_test.go @@ -0,0 +1,28 @@ +//go:build integration + +package repository + +import ( + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func (s *ProxyRepoSuite) TestListWithFiltersAndAccountCount_SortByAccountCountDesc() { + p1 := s.mustCreateProxy(&service.Proxy{Name: "p1", Protocol: "http", Host: "127.0.0.1", Port: 8080, Status: service.StatusActive}) + p2 := s.mustCreateProxy(&service.Proxy{Name: "p2", Protocol: "http", Host: "127.0.0.1", Port: 8081, Status: service.StatusActive}) + s.mustInsertAccount("a1", &p1.ID) + s.mustInsertAccount("a2", &p1.ID) + s.mustInsertAccount("a3", &p2.ID) + + proxies, _, err := s.repo.ListWithFiltersAndAccountCount(s.ctx, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "account_count", + SortOrder: "desc", + }, "", "", "") + s.Require().NoError(err) + s.Require().Len(proxies, 2) + s.Require().Equal(p1.ID, proxies[0].ID) + s.Require().Equal(int64(2), proxies[0].AccountCount) + s.Require().Equal(p2.ID, proxies[1].ID) +} diff --git a/backend/internal/repository/redeem_code_repo.go b/backend/internal/repository/redeem_code_repo.go index 934a30956851aa00a1834d2d91d6b557cbc31ad8..07975970ef86fee547dcd77b26230a6235615a6e 100644 --- a/backend/internal/repository/redeem_code_repo.go +++ b/backend/internal/repository/redeem_code_repo.go @@ -2,6 +2,7 @@ package repository import ( "context" + "strings" "time" dbent "github.com/Wei-Shaw/sub2api/ent" @@ -9,6 +10,8 @@ import ( "github.com/Wei-Shaw/sub2api/ent/user" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/Wei-Shaw/sub2api/internal/service" + + entsql "entgo.io/ent/dialect/sql" ) type redeemCodeRepository struct { @@ -120,13 +123,16 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin return nil, nil, err } - codes, err := q. + codesQuery := q. WithUser(). WithGroup(). Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(redeemcode.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range redeemCodeListOrder(params) { + codesQuery = codesQuery.Order(order) + } + + codes, err := codesQuery.All(ctx) if err != nil { return nil, nil, err } @@ -136,6 +142,34 @@ func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagin return outCodes, paginationResultFromTotal(int64(total), params), nil } +func redeemCodeListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + + var field string + switch sortBy { + case "type": + field = redeemcode.FieldType + case "value": + field = redeemcode.FieldValue + case "status": + field = redeemcode.FieldStatus + case "used_at": + field = redeemcode.FieldUsedAt + case "created_at": + field = redeemcode.FieldCreatedAt + case "code": + field = redeemcode.FieldCode + default: + field = redeemcode.FieldID + } + + if sortOrder == pagination.SortOrderAsc { + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(redeemcode.FieldID)} + } + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(redeemcode.FieldID)} +} + func (r *redeemCodeRepository) Update(ctx context.Context, code *service.RedeemCode) error { up := r.client.RedeemCode.UpdateOneID(code.ID). SetCode(code.Code). diff --git a/backend/internal/repository/redeem_code_repo_sort_integration_test.go b/backend/internal/repository/redeem_code_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..30d32f4cf97a7e551f6a52e3f38670b361a7dfc5 --- /dev/null +++ b/backend/internal/repository/redeem_code_repo_sort_integration_test.go @@ -0,0 +1,24 @@ +//go:build integration + +package repository + +import ( + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func (s *RedeemCodeRepoSuite) TestListWithFilters_SortByValueAsc() { + s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "VALUE-20", Type: service.RedeemTypeBalance, Value: 20, Status: service.StatusUnused})) + s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "VALUE-10", Type: service.RedeemTypeBalance, Value: 10, Status: service.StatusUnused})) + + codes, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "value", + SortOrder: "asc", + }, "", "", "") + s.Require().NoError(err) + s.Require().Len(codes, 2) + s.Require().Equal("VALUE-10", codes[0].Code) + s.Require().Equal("VALUE-20", codes[1].Code) +} diff --git a/backend/internal/repository/scheduler_cache.go b/backend/internal/repository/scheduler_cache.go index 4f447e4fea95c187940b802fb7cba936a907b0b0..e9be8c7a6e0eaa99c4019b91e875ef92642dce9c 100644 --- a/backend/internal/repository/scheduler_cache.go +++ b/backend/internal/repository/scheduler_cache.go @@ -15,19 +15,39 @@ const ( schedulerBucketSetKey = "sched:buckets" schedulerOutboxWatermarkKey = "sched:outbox:watermark" schedulerAccountPrefix = "sched:acc:" + schedulerAccountMetaPrefix = "sched:meta:" schedulerActivePrefix = "sched:active:" schedulerReadyPrefix = "sched:ready:" schedulerVersionPrefix = "sched:ver:" schedulerSnapshotPrefix = "sched:" schedulerLockPrefix = "sched:lock:" + + defaultSchedulerSnapshotMGetChunkSize = 128 + defaultSchedulerSnapshotWriteChunkSize = 256 ) type schedulerCache struct { - rdb *redis.Client + rdb *redis.Client + mgetChunkSize int + writeChunkSize int } func NewSchedulerCache(rdb *redis.Client) service.SchedulerCache { - return &schedulerCache{rdb: rdb} + return newSchedulerCacheWithChunkSizes(rdb, defaultSchedulerSnapshotMGetChunkSize, defaultSchedulerSnapshotWriteChunkSize) +} + +func newSchedulerCacheWithChunkSizes(rdb *redis.Client, mgetChunkSize, writeChunkSize int) service.SchedulerCache { + if mgetChunkSize <= 0 { + mgetChunkSize = defaultSchedulerSnapshotMGetChunkSize + } + if writeChunkSize <= 0 { + writeChunkSize = defaultSchedulerSnapshotWriteChunkSize + } + return &schedulerCache{ + rdb: rdb, + mgetChunkSize: mgetChunkSize, + writeChunkSize: writeChunkSize, + } } func (c *schedulerCache) GetSnapshot(ctx context.Context, bucket service.SchedulerBucket) ([]*service.Account, bool, error) { @@ -65,9 +85,9 @@ func (c *schedulerCache) GetSnapshot(ctx context.Context, bucket service.Schedul keys := make([]string, 0, len(ids)) for _, id := range ids { - keys = append(keys, schedulerAccountKey(id)) + keys = append(keys, schedulerAccountMetaKey(id)) } - values, err := c.rdb.MGet(ctx, keys...).Result() + values, err := c.mgetChunked(ctx, keys) if err != nil { return nil, false, err } @@ -100,14 +120,11 @@ func (c *schedulerCache) SetSnapshot(ctx context.Context, bucket service.Schedul versionStr := strconv.FormatInt(version, 10) snapshotKey := schedulerSnapshotKey(bucket, versionStr) - pipe := c.rdb.Pipeline() - for _, account := range accounts { - payload, err := json.Marshal(account) - if err != nil { - return err - } - pipe.Set(ctx, schedulerAccountKey(strconv.FormatInt(account.ID, 10)), payload, 0) + if err := c.writeAccounts(ctx, accounts); err != nil { + return err } + + pipe := c.rdb.Pipeline() if len(accounts) > 0 { // 使用序号作为 score,保持数据库返回的排序语义。 members := make([]redis.Z, 0, len(accounts)) @@ -117,7 +134,13 @@ func (c *schedulerCache) SetSnapshot(ctx context.Context, bucket service.Schedul Member: strconv.FormatInt(account.ID, 10), }) } - pipe.ZAdd(ctx, snapshotKey, members...) + for start := 0; start < len(members); start += c.writeChunkSize { + end := start + c.writeChunkSize + if end > len(members) { + end = len(members) + } + pipe.ZAdd(ctx, snapshotKey, members[start:end]...) + } } else { pipe.Del(ctx, snapshotKey) } @@ -151,20 +174,15 @@ func (c *schedulerCache) SetAccount(ctx context.Context, account *service.Accoun if account == nil || account.ID <= 0 { return nil } - payload, err := json.Marshal(account) - if err != nil { - return err - } - key := schedulerAccountKey(strconv.FormatInt(account.ID, 10)) - return c.rdb.Set(ctx, key, payload, 0).Err() + return c.writeAccounts(ctx, []service.Account{*account}) } func (c *schedulerCache) DeleteAccount(ctx context.Context, accountID int64) error { if accountID <= 0 { return nil } - key := schedulerAccountKey(strconv.FormatInt(accountID, 10)) - return c.rdb.Del(ctx, key).Err() + id := strconv.FormatInt(accountID, 10) + return c.rdb.Del(ctx, schedulerAccountKey(id), schedulerAccountMetaKey(id)).Err() } func (c *schedulerCache) UpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error { @@ -179,7 +197,7 @@ func (c *schedulerCache) UpdateLastUsed(ctx context.Context, updates map[int64]t ids = append(ids, id) } - values, err := c.rdb.MGet(ctx, keys...).Result() + values, err := c.mgetChunked(ctx, keys) if err != nil { return err } @@ -198,7 +216,12 @@ func (c *schedulerCache) UpdateLastUsed(ctx context.Context, updates map[int64]t if err != nil { return err } + metaPayload, err := json.Marshal(buildSchedulerMetadataAccount(*account)) + if err != nil { + return err + } pipe.Set(ctx, keys[i], updated, 0) + pipe.Set(ctx, schedulerAccountMetaKey(strconv.FormatInt(ids[i], 10)), metaPayload, 0) } _, err = pipe.Exec(ctx) return err @@ -256,6 +279,10 @@ func schedulerAccountKey(id string) string { return schedulerAccountPrefix + id } +func schedulerAccountMetaKey(id string) string { + return schedulerAccountMetaPrefix + id +} + func ptrTime(t time.Time) *time.Time { return &t } @@ -276,3 +303,138 @@ func decodeCachedAccount(val any) (*service.Account, error) { } return &account, nil } + +func (c *schedulerCache) writeAccounts(ctx context.Context, accounts []service.Account) error { + if len(accounts) == 0 { + return nil + } + + pipe := c.rdb.Pipeline() + pending := 0 + flush := func() error { + if pending == 0 { + return nil + } + if _, err := pipe.Exec(ctx); err != nil { + return err + } + pipe = c.rdb.Pipeline() + pending = 0 + return nil + } + + for _, account := range accounts { + fullPayload, err := json.Marshal(account) + if err != nil { + return err + } + metaPayload, err := json.Marshal(buildSchedulerMetadataAccount(account)) + if err != nil { + return err + } + + id := strconv.FormatInt(account.ID, 10) + pipe.Set(ctx, schedulerAccountKey(id), fullPayload, 0) + pipe.Set(ctx, schedulerAccountMetaKey(id), metaPayload, 0) + pending++ + if pending >= c.writeChunkSize { + if err := flush(); err != nil { + return err + } + } + } + + return flush() +} + +func (c *schedulerCache) mgetChunked(ctx context.Context, keys []string) ([]any, error) { + if len(keys) == 0 { + return []any{}, nil + } + + out := make([]any, 0, len(keys)) + chunkSize := c.mgetChunkSize + if chunkSize <= 0 { + chunkSize = defaultSchedulerSnapshotMGetChunkSize + } + for start := 0; start < len(keys); start += chunkSize { + end := start + chunkSize + if end > len(keys) { + end = len(keys) + } + part, err := c.rdb.MGet(ctx, keys[start:end]...).Result() + if err != nil { + return nil, err + } + out = append(out, part...) + } + return out, nil +} + +func buildSchedulerMetadataAccount(account service.Account) service.Account { + return service.Account{ + ID: account.ID, + Name: account.Name, + Platform: account.Platform, + Type: account.Type, + Concurrency: account.Concurrency, + LoadFactor: account.LoadFactor, + Priority: account.Priority, + RateMultiplier: account.RateMultiplier, + Status: account.Status, + LastUsedAt: account.LastUsedAt, + ExpiresAt: account.ExpiresAt, + AutoPauseOnExpired: account.AutoPauseOnExpired, + Schedulable: account.Schedulable, + RateLimitedAt: account.RateLimitedAt, + RateLimitResetAt: account.RateLimitResetAt, + OverloadUntil: account.OverloadUntil, + TempUnschedulableUntil: account.TempUnschedulableUntil, + TempUnschedulableReason: account.TempUnschedulableReason, + SessionWindowStart: account.SessionWindowStart, + SessionWindowEnd: account.SessionWindowEnd, + SessionWindowStatus: account.SessionWindowStatus, + Credentials: filterSchedulerCredentials(account.Credentials), + Extra: filterSchedulerExtra(account.Extra), + } +} + +func filterSchedulerCredentials(credentials map[string]any) map[string]any { + if len(credentials) == 0 { + return nil + } + keys := []string{"model_mapping", "api_key", "project_id", "oauth_type"} + filtered := make(map[string]any) + for _, key := range keys { + if value, ok := credentials[key]; ok && value != nil { + filtered[key] = value + } + } + if len(filtered) == 0 { + return nil + } + return filtered +} + +func filterSchedulerExtra(extra map[string]any) map[string]any { + if len(extra) == 0 { + return nil + } + keys := []string{ + "mixed_scheduling", + "window_cost_limit", + "window_cost_sticky_reserve", + "max_sessions", + "session_idle_timeout_minutes", + } + filtered := make(map[string]any) + for _, key := range keys { + if value, ok := extra[key]; ok && value != nil { + filtered[key] = value + } + } + if len(filtered) == 0 { + return nil + } + return filtered +} diff --git a/backend/internal/repository/scheduler_cache_integration_test.go b/backend/internal/repository/scheduler_cache_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..134a6a0753f04f3a1fa0bc0f184822aa47609578 --- /dev/null +++ b/backend/internal/repository/scheduler_cache_integration_test.go @@ -0,0 +1,88 @@ +//go:build integration + +package repository + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/stretchr/testify/require" +) + +func TestSchedulerCacheSnapshotUsesSlimMetadataButKeepsFullAccount(t *testing.T) { + ctx := context.Background() + rdb := testRedis(t) + cache := NewSchedulerCache(rdb) + + bucket := service.SchedulerBucket{GroupID: 2, Platform: service.PlatformGemini, Mode: service.SchedulerModeSingle} + now := time.Now().UTC().Truncate(time.Second) + limitReset := now.Add(10 * time.Minute) + overloadUntil := now.Add(2 * time.Minute) + tempUnschedUntil := now.Add(3 * time.Minute) + windowEnd := now.Add(5 * time.Hour) + + account := service.Account{ + ID: 101, + Name: "gemini-heavy", + Platform: service.PlatformGemini, + Type: service.AccountTypeOAuth, + Status: service.StatusActive, + Schedulable: true, + Concurrency: 3, + Priority: 7, + LastUsedAt: &now, + Credentials: map[string]any{ + "api_key": "gemini-api-key", + "access_token": "secret-access-token", + "project_id": "proj-1", + "oauth_type": "ai_studio", + "model_mapping": map[string]any{"gemini-2.5-pro": "gemini-2.5-pro"}, + "huge_blob": strings.Repeat("x", 4096), + }, + Extra: map[string]any{ + "mixed_scheduling": true, + "window_cost_limit": 12.5, + "window_cost_sticky_reserve": 8.0, + "max_sessions": 4, + "session_idle_timeout_minutes": 11, + "unused_large_field": strings.Repeat("y", 4096), + }, + RateLimitResetAt: &limitReset, + OverloadUntil: &overloadUntil, + TempUnschedulableUntil: &tempUnschedUntil, + SessionWindowStart: &now, + SessionWindowEnd: &windowEnd, + SessionWindowStatus: "active", + } + + require.NoError(t, cache.SetSnapshot(ctx, bucket, []service.Account{account})) + + snapshot, hit, err := cache.GetSnapshot(ctx, bucket) + require.NoError(t, err) + require.True(t, hit) + require.Len(t, snapshot, 1) + + got := snapshot[0] + require.NotNil(t, got) + require.Equal(t, "gemini-api-key", got.GetCredential("api_key")) + require.Equal(t, "proj-1", got.GetCredential("project_id")) + require.Equal(t, "ai_studio", got.GetCredential("oauth_type")) + require.NotEmpty(t, got.GetModelMapping()) + require.Empty(t, got.GetCredential("access_token")) + require.Empty(t, got.GetCredential("huge_blob")) + require.Equal(t, true, got.Extra["mixed_scheduling"]) + require.Equal(t, 12.5, got.GetWindowCostLimit()) + require.Equal(t, 8.0, got.GetWindowCostStickyReserve()) + require.Equal(t, 4, got.GetMaxSessions()) + require.Equal(t, 11, got.GetSessionIdleTimeoutMinutes()) + require.Nil(t, got.Extra["unused_large_field"]) + + full, err := cache.GetAccount(ctx, account.ID) + require.NoError(t, err) + require.NotNil(t, full) + require.Equal(t, "secret-access-token", full.GetCredential("access_token")) + require.Equal(t, strings.Repeat("x", 4096), full.GetCredential("huge_blob")) +} diff --git a/backend/internal/repository/usage_log_repo.go b/backend/internal/repository/usage_log_repo.go index d7bcd0944d06673321412e024186909859486723..3ba2191e50298e7835874b2a09c7c045aa872839 100644 --- a/backend/internal/repository/usage_log_repo.go +++ b/backend/internal/repository/usage_log_repo.go @@ -3771,7 +3771,7 @@ func (r *usageLogRepository) listUsageLogsWithPagination(ctx context.Context, wh limitPos := len(args) + 1 offsetPos := len(args) + 2 listArgs := append(append([]any{}, args...), params.Limit(), params.Offset()) - query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY id DESC LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, limitPos, offsetPos) + query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY %s LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, usageLogOrderBy(params), limitPos, offsetPos) logs, err := r.queryUsageLogs(ctx, query, listArgs...) if err != nil { return nil, nil, err @@ -3786,7 +3786,7 @@ func (r *usageLogRepository) listUsageLogsWithFastPagination(ctx context.Context limitPos := len(args) + 1 offsetPos := len(args) + 2 listArgs := append(append([]any{}, args...), limit+1, offset) - query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY id DESC LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, limitPos, offsetPos) + query := fmt.Sprintf("SELECT %s FROM usage_logs %s ORDER BY %s LIMIT $%d OFFSET $%d", usageLogSelectColumns, whereClause, usageLogOrderBy(params), limitPos, offsetPos) logs, err := r.queryUsageLogs(ctx, query, listArgs...) if err != nil { @@ -3808,6 +3808,26 @@ func (r *usageLogRepository) listUsageLogsWithFastPagination(ctx context.Context return logs, paginationResultFromTotal(total, params), nil } +func usageLogOrderBy(params pagination.PaginationParams) string { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := strings.ToUpper(params.NormalizedSortOrder(pagination.SortOrderDesc)) + + var column string + switch sortBy { + case "model": + column = "COALESCE(NULLIF(TRIM(requested_model), ''), model)" + case "created_at": + column = "created_at" + default: + column = "id" + } + + if column == "id" { + return fmt.Sprintf("id %s", sortOrder) + } + return fmt.Sprintf("%s %s, id %s", column, sortOrder, sortOrder) +} + func (r *usageLogRepository) queryUsageLogs(ctx context.Context, query string, args ...any) (logs []service.UsageLog, err error) { rows, err := r.sql.QueryContext(ctx, query, args...) if err != nil { diff --git a/backend/internal/repository/usage_log_repo_request_type_test.go b/backend/internal/repository/usage_log_repo_request_type_test.go index ce0c5f004029cc84d33d7285dc2cd7fc65713c72..b9cb6a13302a8eafd6ff3ee30728fb7d505d5bda 100644 --- a/backend/internal/repository/usage_log_repo_request_type_test.go +++ b/backend/internal/repository/usage_log_repo_request_type_test.go @@ -330,6 +330,15 @@ func TestUsageLogRepositoryGetStatsWithFiltersRequestTypePriority(t *testing.T) "total_account_cost", "avg_duration_ms", }).AddRow(int64(1), int64(2), int64(3), int64(4), 1.2, 1.0, 1.2, 20.0)) + mock.ExpectQuery("SELECT COALESCE\\(NULLIF\\(TRIM\\(inbound_endpoint\\), ''\\), 'unknown'\\) AS endpoint"). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), requestType). + WillReturnRows(sqlmock.NewRows([]string{"endpoint", "requests", "total_tokens", "cost", "actual_cost"})) + mock.ExpectQuery("SELECT COALESCE\\(NULLIF\\(TRIM\\(upstream_endpoint\\), ''\\), 'unknown'\\) AS endpoint"). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), requestType). + WillReturnRows(sqlmock.NewRows([]string{"endpoint", "requests", "total_tokens", "cost", "actual_cost"})) + mock.ExpectQuery("SELECT CONCAT\\("). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), requestType). + WillReturnRows(sqlmock.NewRows([]string{"endpoint", "requests", "total_tokens", "cost", "actual_cost"})) stats, err := repo.GetStatsWithFilters(context.Background(), filters) require.NoError(t, err) diff --git a/backend/internal/repository/usage_log_repo_sort_integration_test.go b/backend/internal/repository/usage_log_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4c69f97538ef2dfac45b1a06617544e943c76621 --- /dev/null +++ b/backend/internal/repository/usage_log_repo_sort_integration_test.go @@ -0,0 +1,61 @@ +//go:build integration + +package repository + +import ( + "time" + + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/pkg/usagestats" + "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/google/uuid" +) + +func (s *UsageLogRepoSuite) TestListWithFilters_SortByModelAsc() { + user := mustCreateUser(s.T(), s.client, &service.User{Email: "usage-sort@example.com"}) + apiKey := mustCreateApiKey(s.T(), s.client, &service.APIKey{UserID: user.ID, Key: "sk-usage-sort", Name: "k"}) + account := mustCreateAccount(s.T(), s.client, &service.Account{Name: "usage-sort-account"}) + + first := &service.UsageLog{ + UserID: user.ID, + APIKeyID: apiKey.ID, + AccountID: account.ID, + RequestID: uuid.New().String(), + Model: "z-model", + RequestedModel: "z-model", + InputTokens: 10, + OutputTokens: 20, + TotalCost: 0.5, + ActualCost: 0.5, + CreatedAt: time.Now(), + } + _, err := s.repo.Create(s.ctx, first) + s.Require().NoError(err) + + second := &service.UsageLog{ + UserID: user.ID, + APIKeyID: apiKey.ID, + AccountID: account.ID, + RequestID: uuid.New().String(), + Model: "a-model", + RequestedModel: "a-model", + InputTokens: 10, + OutputTokens: 20, + TotalCost: 0.5, + ActualCost: 0.5, + CreatedAt: time.Now().Add(time.Second), + } + _, err = s.repo.Create(s.ctx, second) + s.Require().NoError(err) + + logs, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "model", + SortOrder: "asc", + }, usagestats.UsageLogFilters{UserID: user.ID}) + s.Require().NoError(err) + s.Require().Len(logs, 2) + s.Require().Equal("a-model", logs[0].RequestedModel) + s.Require().Equal("z-model", logs[1].RequestedModel) +} diff --git a/backend/internal/repository/user_repo.go b/backend/internal/repository/user_repo.go index 06c79113e2e2df4956884b8a26b8e0786510b0b8..d5a13607aef5eb743530120eab2d2be77f5e94fc 100644 --- a/backend/internal/repository/user_repo.go +++ b/backend/internal/repository/user_repo.go @@ -17,6 +17,8 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usersubscription" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/Wei-Shaw/sub2api/internal/service" + + entsql "entgo.io/ent/dialect/sql" ) type userRepository struct { @@ -224,11 +226,14 @@ func (r *userRepository) ListWithFilters(ctx context.Context, params pagination. return nil, nil, err } - users, err := q. + usersQuery := q. Offset(params.Offset()). - Limit(params.Limit()). - Order(dbent.Desc(dbuser.FieldID)). - All(ctx) + Limit(params.Limit()) + for _, order := range userListOrder(params) { + usersQuery = usersQuery.Order(order) + } + + users, err := usersQuery.All(ctx) if err != nil { return nil, nil, err } @@ -281,6 +286,50 @@ func (r *userRepository) ListWithFilters(ctx context.Context, params pagination. return outUsers, paginationResultFromTotal(int64(total), params), nil } +func userListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { + sortBy := strings.ToLower(strings.TrimSpace(params.SortBy)) + sortOrder := params.NormalizedSortOrder(pagination.SortOrderDesc) + + var field string + defaultField := true + switch sortBy { + case "email": + field = dbuser.FieldEmail + defaultField = false + case "username": + field = dbuser.FieldUsername + defaultField = false + case "role": + field = dbuser.FieldRole + defaultField = false + case "balance": + field = dbuser.FieldBalance + defaultField = false + case "concurrency": + field = dbuser.FieldConcurrency + defaultField = false + case "status": + field = dbuser.FieldStatus + defaultField = false + case "created_at": + field = dbuser.FieldCreatedAt + defaultField = false + default: + field = dbuser.FieldID + } + + if sortOrder == pagination.SortOrderAsc { + if defaultField && field == dbuser.FieldID { + return []func(*entsql.Selector){dbent.Asc(dbuser.FieldID)} + } + return []func(*entsql.Selector){dbent.Asc(field), dbent.Asc(dbuser.FieldID)} + } + if defaultField && field == dbuser.FieldID { + return []func(*entsql.Selector){dbent.Desc(dbuser.FieldID)} + } + return []func(*entsql.Selector){dbent.Desc(field), dbent.Desc(dbuser.FieldID)} +} + // filterUsersByAttributes returns user IDs that match ALL the given attribute filters func (r *userRepository) filterUsersByAttributes(ctx context.Context, attrs map[int64]string) ([]int64, error) { if len(attrs) == 0 { diff --git a/backend/internal/repository/user_repo_sort_integration_test.go b/backend/internal/repository/user_repo_sort_integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ab84b0e93bb969bfaf1c863cdb30067cc15a8942 --- /dev/null +++ b/backend/internal/repository/user_repo_sort_integration_test.go @@ -0,0 +1,39 @@ +//go:build integration + +package repository + +import ( + "testing" + + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func (s *UserRepoSuite) TestListWithFilters_SortByEmailAsc() { + s.mustCreateUser(&service.User{Email: "z-last@example.com", Username: "z-user"}) + s.mustCreateUser(&service.User{Email: "a-first@example.com", Username: "a-user"}) + + users, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{ + Page: 1, + PageSize: 10, + SortBy: "email", + SortOrder: "asc", + }, service.UserListFilters{}) + s.Require().NoError(err) + s.Require().Len(users, 2) + s.Require().Equal("a-first@example.com", users[0].Email) + s.Require().Equal("z-last@example.com", users[1].Email) +} + +func (s *UserRepoSuite) TestList_DefaultSortByNewestFirst() { + first := s.mustCreateUser(&service.User{Email: "first@example.com"}) + second := s.mustCreateUser(&service.User{Email: "second@example.com"}) + + users, _, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}) + s.Require().NoError(err) + s.Require().Len(users, 2) + s.Require().Equal(second.ID, users[0].ID) + s.Require().Equal(first.ID, users[1].ID) +} + +func TestUserRepoSortSuiteSmoke(_ *testing.T) {} diff --git a/backend/internal/repository/wire.go b/backend/internal/repository/wire.go index 657e3ed66cda3d408ccd810b02dc6c125bac28d7..d3adb4a0aa7b52daf523627c72f2b4ebdff35523 100644 --- a/backend/internal/repository/wire.go +++ b/backend/internal/repository/wire.go @@ -47,6 +47,21 @@ func ProvideSessionLimitCache(rdb *redis.Client, cfg *config.Config) service.Ses return NewSessionLimitCache(rdb, defaultIdleTimeoutMinutes) } +// ProvideSchedulerCache 创建调度快照缓存,并注入快照分块参数。 +func ProvideSchedulerCache(rdb *redis.Client, cfg *config.Config) service.SchedulerCache { + mgetChunkSize := defaultSchedulerSnapshotMGetChunkSize + writeChunkSize := defaultSchedulerSnapshotWriteChunkSize + if cfg != nil { + if cfg.Gateway.Scheduling.SnapshotMGetChunkSize > 0 { + mgetChunkSize = cfg.Gateway.Scheduling.SnapshotMGetChunkSize + } + if cfg.Gateway.Scheduling.SnapshotWriteChunkSize > 0 { + writeChunkSize = cfg.Gateway.Scheduling.SnapshotWriteChunkSize + } + } + return newSchedulerCacheWithChunkSizes(rdb, mgetChunkSize, writeChunkSize) +} + // ProviderSet is the Wire provider set for all repositories var ProviderSet = wire.NewSet( NewUserRepository, @@ -92,7 +107,7 @@ var ProviderSet = wire.NewSet( NewRedeemCache, NewUpdateCache, NewGeminiTokenCache, - NewSchedulerCache, + ProvideSchedulerCache, NewSchedulerOutboxRepository, NewProxyLatencyCache, NewTotpCache, diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go index 24f60f2705f7042786a9497e8e02d25dc576510f..1a4892fa387d2c299cd8f893389cffac75dfafa2 100644 --- a/backend/internal/server/api_contract_test.go +++ b/backend/internal/server/api_contract_test.go @@ -462,6 +462,28 @@ func TestAPIContracts(t *testing.T) { service.SettingKeyTurnstileSiteKey: "site-key", service.SettingKeyTurnstileSecretKey: "secret-key", + service.SettingKeyOIDCConnectEnabled: "false", + service.SettingKeyOIDCConnectProviderName: "OIDC", + service.SettingKeyOIDCConnectClientID: "", + service.SettingKeyOIDCConnectIssuerURL: "", + service.SettingKeyOIDCConnectDiscoveryURL: "", + service.SettingKeyOIDCConnectAuthorizeURL: "", + service.SettingKeyOIDCConnectTokenURL: "", + service.SettingKeyOIDCConnectUserInfoURL: "", + service.SettingKeyOIDCConnectJWKSURL: "", + service.SettingKeyOIDCConnectScopes: "openid email profile", + service.SettingKeyOIDCConnectRedirectURL: "", + service.SettingKeyOIDCConnectFrontendRedirectURL: "/auth/oidc/callback", + service.SettingKeyOIDCConnectTokenAuthMethod: "client_secret_post", + service.SettingKeyOIDCConnectUsePKCE: "false", + service.SettingKeyOIDCConnectValidateIDToken: "true", + service.SettingKeyOIDCConnectAllowedSigningAlgs: "RS256,ES256,PS256", + service.SettingKeyOIDCConnectClockSkewSeconds: "120", + service.SettingKeyOIDCConnectRequireEmailVerified: "false", + service.SettingKeyOIDCConnectUserInfoEmailPath: "", + service.SettingKeyOIDCConnectUserInfoIDPath: "", + service.SettingKeyOIDCConnectUserInfoUsernamePath: "", + service.SettingKeySiteName: "Sub2API", service.SettingKeySiteLogo: "", service.SettingKeySiteSubtitle: "Subtitle", @@ -469,8 +491,10 @@ func TestAPIContracts(t *testing.T) { service.SettingKeyContactInfo: "support", service.SettingKeyDocURL: "https://docs.example.com", - service.SettingKeyDefaultConcurrency: "5", - service.SettingKeyDefaultBalance: "1.25", + service.SettingKeyDefaultConcurrency: "5", + service.SettingKeyDefaultBalance: "1.25", + service.SettingKeyTableDefaultPageSize: "20", + service.SettingKeyTablePageSizeOptions: "[10,20,50,100]", service.SettingKeyOpsMonitoringEnabled: "false", service.SettingKeyOpsRealtimeMonitoringEnabled: "true", @@ -503,10 +527,32 @@ func TestAPIContracts(t *testing.T) { "turnstile_enabled": true, "turnstile_site_key": "site-key", "turnstile_secret_key_configured": true, - "linuxdo_connect_enabled": false, + "linuxdo_connect_enabled": false, "linuxdo_connect_client_id": "", "linuxdo_connect_client_secret_configured": false, "linuxdo_connect_redirect_url": "", + "oidc_connect_enabled": false, + "oidc_connect_provider_name": "OIDC", + "oidc_connect_client_id": "", + "oidc_connect_client_secret_configured": false, + "oidc_connect_issuer_url": "", + "oidc_connect_discovery_url": "", + "oidc_connect_authorize_url": "", + "oidc_connect_token_url": "", + "oidc_connect_userinfo_url": "", + "oidc_connect_jwks_url": "", + "oidc_connect_scopes": "openid email profile", + "oidc_connect_redirect_url": "", + "oidc_connect_frontend_redirect_url": "/auth/oidc/callback", + "oidc_connect_token_auth_method": "client_secret_post", + "oidc_connect_use_pkce": false, + "oidc_connect_validate_id_token": true, + "oidc_connect_allowed_signing_algs": "RS256,ES256,PS256", + "oidc_connect_clock_skew_seconds": 120, + "oidc_connect_require_email_verified": false, + "oidc_connect_userinfo_email_path": "", + "oidc_connect_userinfo_id_path": "", + "oidc_connect_userinfo_username_path": "", "ops_monitoring_enabled": false, "ops_realtime_monitoring_enabled": true, "ops_query_mode_default": "auto", @@ -532,6 +578,8 @@ func TestAPIContracts(t *testing.T) { "hide_ccs_import_button": false, "purchase_subscription_enabled": false, "purchase_subscription_url": "", + "table_default_page_size": 20, + "table_page_size_options": [10, 20, 50, 100], "min_claude_code_version": "", "max_claude_code_version": "", "allow_ungrouped_key_scheduling": false, @@ -539,6 +587,24 @@ func TestAPIContracts(t *testing.T) { "enable_cch_signing": false, "enable_fingerprint_unification": true, "enable_metadata_passthrough": false, + "payment_enabled": false, + "payment_min_amount": 0, + "payment_max_amount": 0, + "payment_daily_limit": 0, + "payment_order_timeout_minutes": 0, + "payment_max_pending_orders": 0, + "payment_enabled_types": null, + "payment_balance_disabled": false, + "payment_load_balance_strategy": "", + "payment_product_name_prefix": "", + "payment_product_name_suffix": "", + "payment_help_image_url": "", + "payment_help_text": "", + "payment_cancel_rate_limit_enabled": false, + "payment_cancel_rate_limit_max": 0, + "payment_cancel_rate_limit_window": 0, + "payment_cancel_rate_limit_unit": "", + "payment_cancel_rate_limit_window_mode": "", "custom_menu_items": [], "custom_endpoints": [] } @@ -652,7 +718,7 @@ func newContractDeps(t *testing.T) *contractDeps { authHandler := handler.NewAuthHandler(cfg, nil, userService, settingService, nil, redeemService, nil) apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService) usageHandler := handler.NewUsageHandler(usageService, apiKeyService) - adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil, nil, nil) + adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil, nil, nil, nil, nil) adminAccountHandler := adminhandler.NewAccountHandler(adminService, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) jwtAuth := func(c *gin.Context) { diff --git a/backend/internal/server/router.go b/backend/internal/server/router.go index d60a142c873f06942e5a623e862a2b321d54f985..a507b6f831e2fe69163eff38f32ba610067506d0 100644 --- a/backend/internal/server/router.go +++ b/backend/internal/server/router.go @@ -111,4 +111,5 @@ func registerRoutes( routes.RegisterUserRoutes(v1, h, jwtAuth, settingService) routes.RegisterAdminRoutes(v1, h, adminAuth) routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg) + routes.RegisterPaymentRoutes(v1, h.Payment, h.PaymentWebhook, h.Admin.Payment, jwtAuth, adminAuth, settingService) } diff --git a/backend/internal/server/routes/auth.go b/backend/internal/server/routes/auth.go index a6c0ecf568341798f9b356de228bd194cbcca4ea..c143b030fc88da423867e4bf77404474d9039f78 100644 --- a/backend/internal/server/routes/auth.go +++ b/backend/internal/server/routes/auth.go @@ -70,6 +70,14 @@ func RegisterAuthRoutes( }), h.Auth.CompleteLinuxDoOAuthRegistration, ) + auth.GET("/oauth/oidc/start", h.Auth.OIDCOAuthStart) + auth.GET("/oauth/oidc/callback", h.Auth.OIDCOAuthCallback) + auth.POST("/oauth/oidc/complete-registration", + rateLimiter.LimitWithOptions("oauth-oidc-complete", 10, time.Minute, middleware.RateLimitOptions{ + FailureMode: middleware.RateLimitFailClose, + }), + h.Auth.CompleteOIDCOAuthRegistration, + ) } // 公开设置(无需认证) diff --git a/backend/internal/server/routes/payment.go b/backend/internal/server/routes/payment.go new file mode 100644 index 0000000000000000000000000000000000000000..6bf04679d417dc2c493934ba16ecb619e8312660 --- /dev/null +++ b/backend/internal/server/routes/payment.go @@ -0,0 +1,103 @@ +package routes + +import ( + "github.com/Wei-Shaw/sub2api/internal/handler" + "github.com/Wei-Shaw/sub2api/internal/handler/admin" + "github.com/Wei-Shaw/sub2api/internal/server/middleware" + "github.com/Wei-Shaw/sub2api/internal/service" + + "github.com/gin-gonic/gin" +) + +// RegisterPaymentRoutes registers all payment-related routes: +// user-facing endpoints, webhook endpoints, and admin endpoints. +func RegisterPaymentRoutes( + v1 *gin.RouterGroup, + paymentHandler *handler.PaymentHandler, + webhookHandler *handler.PaymentWebhookHandler, + adminPaymentHandler *admin.PaymentHandler, + jwtAuth middleware.JWTAuthMiddleware, + adminAuth middleware.AdminAuthMiddleware, + settingService *service.SettingService, +) { + // --- User-facing payment endpoints (authenticated) --- + authenticated := v1.Group("/payment") + authenticated.Use(gin.HandlerFunc(jwtAuth)) + authenticated.Use(middleware.BackendModeUserGuard(settingService)) + { + authenticated.GET("/config", paymentHandler.GetPaymentConfig) + authenticated.GET("/checkout-info", paymentHandler.GetCheckoutInfo) + authenticated.GET("/plans", paymentHandler.GetPlans) + authenticated.GET("/channels", paymentHandler.GetChannels) + authenticated.GET("/limits", paymentHandler.GetLimits) + + orders := authenticated.Group("/orders") + { + orders.POST("", paymentHandler.CreateOrder) + orders.POST("/verify", paymentHandler.VerifyOrder) + orders.GET("/my", paymentHandler.GetMyOrders) + orders.GET("/:id", paymentHandler.GetOrder) + orders.POST("/:id/cancel", paymentHandler.CancelOrder) + orders.POST("/:id/refund-request", paymentHandler.RequestRefund) + } + } + + // --- Public payment endpoints (no auth) --- + // Payment result page needs to verify order status without login + // (user session may have expired during provider redirect). + public := v1.Group("/payment/public") + { + public.POST("/orders/verify", paymentHandler.VerifyOrderPublic) + } + + // --- Webhook endpoints (no auth) --- + webhook := v1.Group("/payment/webhook") + { + // EasyPay sends GET callbacks with query params + webhook.GET("/easypay", webhookHandler.EasyPayNotify) + webhook.POST("/easypay", webhookHandler.EasyPayNotify) + webhook.POST("/alipay", webhookHandler.AlipayNotify) + webhook.POST("/wxpay", webhookHandler.WxpayNotify) + webhook.POST("/stripe", webhookHandler.StripeWebhook) + } + + // --- Admin payment endpoints (admin auth) --- + adminGroup := v1.Group("/admin/payment") + adminGroup.Use(gin.HandlerFunc(adminAuth)) + { + // Dashboard + adminGroup.GET("/dashboard", adminPaymentHandler.GetDashboard) + + // Config + adminGroup.GET("/config", adminPaymentHandler.GetConfig) + adminGroup.PUT("/config", adminPaymentHandler.UpdateConfig) + + // Orders + adminOrders := adminGroup.Group("/orders") + { + adminOrders.GET("", adminPaymentHandler.ListOrders) + adminOrders.GET("/:id", adminPaymentHandler.GetOrderDetail) + adminOrders.POST("/:id/cancel", adminPaymentHandler.CancelOrder) + adminOrders.POST("/:id/retry", adminPaymentHandler.RetryFulfillment) + adminOrders.POST("/:id/refund", adminPaymentHandler.ProcessRefund) + } + + // Subscription Plans + plans := adminGroup.Group("/plans") + { + plans.GET("", adminPaymentHandler.ListPlans) + plans.POST("", adminPaymentHandler.CreatePlan) + plans.PUT("/:id", adminPaymentHandler.UpdatePlan) + plans.DELETE("/:id", adminPaymentHandler.DeletePlan) + } + + // Provider Instances + providers := adminGroup.Group("/providers") + { + providers.GET("", adminPaymentHandler.ListProviders) + providers.POST("", adminPaymentHandler.CreateProvider) + providers.PUT("/:id", adminPaymentHandler.UpdateProvider) + providers.DELETE("/:id", adminPaymentHandler.DeleteProvider) + } + } +} diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index 8032f8717a5597aab7dea14f3b7a0b15522b9eb9..97b42c2458333774c6667633cc4bf1f0db961ff2 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -21,13 +21,13 @@ import ( // AdminService interface defines admin management operations type AdminService interface { // User management - ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters) ([]User, int64, error) + ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters, sortBy, sortOrder string) ([]User, int64, error) GetUser(ctx context.Context, id int64) (*User, error) CreateUser(ctx context.Context, input *CreateUserInput) (*User, error) UpdateUser(ctx context.Context, id int64, input *UpdateUserInput) (*User, error) DeleteUser(ctx context.Context, id int64) error UpdateUserBalance(ctx context.Context, userID int64, balance float64, operation string, notes string) (*User, error) - GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]APIKey, int64, error) + GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]APIKey, int64, error) GetUserUsageStats(ctx context.Context, userID int64, period string) (any, error) // GetUserBalanceHistory returns paginated balance/concurrency change records for a user. // codeType is optional - pass empty string to return all types. @@ -35,7 +35,7 @@ type AdminService interface { GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]RedeemCode, int64, float64, error) // Group management - ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]Group, int64, error) + ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error) GetAllGroups(ctx context.Context) ([]Group, error) GetAllGroupsByPlatform(ctx context.Context, platform string) ([]Group, error) GetGroup(ctx context.Context, id int64) (*Group, error) @@ -55,7 +55,7 @@ type AdminService interface { ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*ReplaceUserGroupResult, error) // Account management - ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, int64, error) + ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]Account, int64, error) GetAccount(ctx context.Context, id int64) (*Account, error) GetAccountsByIDs(ctx context.Context, ids []int64) ([]*Account, error) CreateAccount(ctx context.Context, input *CreateAccountInput) (*Account, error) @@ -77,8 +77,8 @@ type AdminService interface { CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error // Proxy management - ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]Proxy, int64, error) - ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]ProxyWithAccountCount, int64, error) + ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error) + ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]ProxyWithAccountCount, int64, error) GetAllProxies(ctx context.Context) ([]Proxy, error) GetAllProxiesWithAccountCount(ctx context.Context) ([]ProxyWithAccountCount, error) GetProxy(ctx context.Context, id int64) (*Proxy, error) @@ -93,7 +93,7 @@ type AdminService interface { CheckProxyQuality(ctx context.Context, id int64) (*ProxyQualityCheckResult, error) // Redeem code management - ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]RedeemCode, int64, error) + ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]RedeemCode, int64, error) GetRedeemCode(ctx context.Context, id int64) (*RedeemCode, error) GenerateRedeemCodes(ctx context.Context, input *GenerateRedeemCodesInput) ([]RedeemCode, error) DeleteRedeemCode(ctx context.Context, id int64) error @@ -152,10 +152,11 @@ type CreateGroupInput struct { // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes []string // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch bool - DefaultMappedModel string - RequireOAuthOnly bool - RequirePrivacySet bool + AllowMessagesDispatch bool + DefaultMappedModel string + RequireOAuthOnly bool + RequirePrivacySet bool + MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig // 从指定分组复制账号(创建分组后在同一事务内绑定) CopyAccountsFromGroupIDs []int64 } @@ -186,10 +187,11 @@ type UpdateGroupInput struct { // 支持的模型系列(仅 antigravity 平台使用) SupportedModelScopes *[]string // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch *bool - DefaultMappedModel *string - RequireOAuthOnly *bool - RequirePrivacySet *bool + AllowMessagesDispatch *bool + DefaultMappedModel *string + RequireOAuthOnly *bool + RequirePrivacySet *bool + MessagesDispatchModelConfig *OpenAIMessagesDispatchModelConfig // 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号) CopyAccountsFromGroupIDs []int64 } @@ -483,8 +485,8 @@ func NewAdminService( } // User management implementations -func (s *adminServiceImpl) ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters) ([]User, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) ListUsers(ctx context.Context, page, pageSize int, filters UserListFilters, sortBy, sortOrder string) ([]User, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} users, result, err := s.userRepo.ListWithFilters(ctx, params, filters) if err != nil { return nil, 0, err @@ -751,8 +753,8 @@ func (s *adminServiceImpl) UpdateUserBalance(ctx context.Context, userID int64, return user, nil } -func (s *adminServiceImpl) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int) ([]APIKey, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]APIKey, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} keys, result, err := s.apiKeyRepo.ListByUserID(ctx, userID, params, APIKeyListFilters{}) if err != nil { return nil, 0, err @@ -787,8 +789,8 @@ func (s *adminServiceImpl) GetUserBalanceHistory(ctx context.Context, userID int } // Group management implementations -func (s *adminServiceImpl) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool) ([]Group, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]Group, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} groups, result, err := s.groupRepo.ListWithFilters(ctx, params, platform, status, search, isExclusive) if err != nil { return nil, 0, err @@ -908,7 +910,9 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn RequireOAuthOnly: input.RequireOAuthOnly, RequirePrivacySet: input.RequirePrivacySet, DefaultMappedModel: input.DefaultMappedModel, + MessagesDispatchModelConfig: normalizeOpenAIMessagesDispatchModelConfig(input.MessagesDispatchModelConfig), } + sanitizeGroupMessagesDispatchFields(group) if err := s.groupRepo.Create(ctx, group); err != nil { return nil, err } @@ -1135,6 +1139,10 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd if input.DefaultMappedModel != nil { group.DefaultMappedModel = *input.DefaultMappedModel } + if input.MessagesDispatchModelConfig != nil { + group.MessagesDispatchModelConfig = normalizeOpenAIMessagesDispatchModelConfig(*input.MessagesDispatchModelConfig) + } + sanitizeGroupMessagesDispatchFields(group) if err := s.groupRepo.Update(ctx, group); err != nil { return nil, err @@ -1456,8 +1464,8 @@ func (s *adminServiceImpl) ReplaceUserGroup(ctx context.Context, userID, oldGrou } // Account management implementations -func (s *adminServiceImpl) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]Account, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} accounts, result, err := s.accountRepo.ListWithFilters(ctx, params, platform, accountType, status, search, groupID, privacyMode) if err != nil { return nil, 0, err @@ -1885,8 +1893,8 @@ func (s *adminServiceImpl) SetAccountSchedulable(ctx context.Context, id int64, } // Proxy management implementations -func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string) ([]Proxy, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} proxies, result, err := s.proxyRepo.ListWithFilters(ctx, params, protocol, status, search) if err != nil { return nil, 0, err @@ -1894,8 +1902,8 @@ func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int, return proxies, result.Total, nil } -func (s *adminServiceImpl) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string) ([]ProxyWithAccountCount, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]ProxyWithAccountCount, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} proxies, result, err := s.proxyRepo.ListWithFiltersAndAccountCount(ctx, params, protocol, status, search) if err != nil { return nil, 0, err @@ -2032,8 +2040,8 @@ func (s *adminServiceImpl) CheckProxyExists(ctx context.Context, host string, po } // Redeem code management implementations -func (s *adminServiceImpl) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string) ([]RedeemCode, int64, error) { - params := pagination.PaginationParams{Page: page, PageSize: pageSize} +func (s *adminServiceImpl) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]RedeemCode, int64, error) { + params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder} codes, result, err := s.redeemCodeRepo.ListWithFilters(ctx, params, codeType, status, search) if err != nil { return nil, 0, err diff --git a/backend/internal/service/admin_service_group_test.go b/backend/internal/service/admin_service_group_test.go index 536be0b5834ab12a72e7a49a16c23b452ff14107..a4c6d0caba6ef7dc4b4594d20adaf4c13b315a1b 100644 --- a/backend/internal/service/admin_service_group_test.go +++ b/backend/internal/service/admin_service_group_test.go @@ -10,6 +10,11 @@ import ( "github.com/stretchr/testify/require" ) +func ptrString[T ~string](v T) *string { + s := string(v) + return &s +} + // groupRepoStubForAdmin 用于测试 AdminService 的 GroupRepository Stub type groupRepoStubForAdmin struct { created *Group // 记录 Create 调用的参数 @@ -120,6 +125,22 @@ func (s *groupRepoStubForAdmin) UpdateSortOrders(_ context.Context, _ []GroupSor return nil } +func TestAdminService_ListGroups_PassesSortParams(t *testing.T) { + repo := &groupRepoStubForAdmin{ + listWithFiltersGroups: []Group{{ID: 1, Name: "g1"}}, + } + svc := &adminServiceImpl{groupRepo: repo} + + _, _, err := svc.ListGroups(context.Background(), 3, 25, PlatformOpenAI, StatusActive, "needle", nil, "account_count", "ASC") + require.NoError(t, err) + require.Equal(t, pagination.PaginationParams{ + Page: 3, + PageSize: 25, + SortBy: "account_count", + SortOrder: "ASC", + }, repo.listWithFiltersParams) +} + // TestAdminService_CreateGroup_WithImagePricing 测试创建分组时 ImagePrice 字段正确传递 func TestAdminService_CreateGroup_WithImagePricing(t *testing.T) { repo := &groupRepoStubForAdmin{} @@ -245,6 +266,116 @@ func TestAdminService_UpdateGroup_PartialImagePricing(t *testing.T) { require.Nil(t, repo.updated.ImagePrice4K) } +func TestAdminService_CreateGroup_NormalizesMessagesDispatchModelConfig(t *testing.T) { + repo := &groupRepoStubForAdmin{} + svc := &adminServiceImpl{groupRepo: repo} + + group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{ + Name: "dispatch-group", + Description: "dispatch config", + Platform: PlatformOpenAI, + RateMultiplier: 1.0, + MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: " gpt-5.4-high ", + SonnetMappedModel: " gpt-5.3-codex ", + HaikuMappedModel: " gpt-5.4-mini-medium ", + ExactModelMappings: map[string]string{ + " claude-sonnet-4-5-20250929 ": " gpt-5.2-high ", + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, group) + require.NotNil(t, repo.created) + require.Equal(t, OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: "gpt-5.4-mini", + ExactModelMappings: map[string]string{ + "claude-sonnet-4-5-20250929": "gpt-5.2", + }, + }, repo.created.MessagesDispatchModelConfig) +} + +func TestAdminService_UpdateGroup_NormalizesMessagesDispatchModelConfig(t *testing.T) { + existingGroup := &Group{ + ID: 1, + Name: "existing-group", + Platform: PlatformOpenAI, + Status: StatusActive, + } + repo := &groupRepoStubForAdmin{getByID: existingGroup} + svc := &adminServiceImpl{groupRepo: repo} + + group, err := svc.UpdateGroup(context.Background(), 1, &UpdateGroupInput{ + MessagesDispatchModelConfig: &OpenAIMessagesDispatchModelConfig{ + SonnetMappedModel: " gpt-5.4-medium ", + ExactModelMappings: map[string]string{ + " claude-haiku-4-5-20251001 ": " gpt-5.4-mini-high ", + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, group) + require.NotNil(t, repo.updated) + require.Equal(t, OpenAIMessagesDispatchModelConfig{ + SonnetMappedModel: "gpt-5.4", + ExactModelMappings: map[string]string{ + "claude-haiku-4-5-20251001": "gpt-5.4-mini", + }, + }, repo.updated.MessagesDispatchModelConfig) +} + +func TestAdminService_CreateGroup_ClearsMessagesDispatchFieldsForNonOpenAIPlatform(t *testing.T) { + repo := &groupRepoStubForAdmin{} + svc := &adminServiceImpl{groupRepo: repo} + + group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{ + Name: "anthropic-group", + Description: "non-openai", + Platform: PlatformAnthropic, + RateMultiplier: 1.0, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4", + }, + }) + require.NoError(t, err) + require.NotNil(t, group) + require.NotNil(t, repo.created) + require.False(t, repo.created.AllowMessagesDispatch) + require.Empty(t, repo.created.DefaultMappedModel) + require.Equal(t, OpenAIMessagesDispatchModelConfig{}, repo.created.MessagesDispatchModelConfig) +} + +func TestAdminService_UpdateGroup_ClearsMessagesDispatchFieldsWhenPlatformChangesAwayFromOpenAI(t *testing.T) { + existingGroup := &Group{ + ID: 1, + Name: "existing-openai-group", + Platform: PlatformOpenAI, + Status: StatusActive, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{ + SonnetMappedModel: "gpt-5.3-codex", + }, + } + repo := &groupRepoStubForAdmin{getByID: existingGroup} + svc := &adminServiceImpl{groupRepo: repo} + + group, err := svc.UpdateGroup(context.Background(), 1, &UpdateGroupInput{ + Platform: PlatformAnthropic, + }) + require.NoError(t, err) + require.NotNil(t, group) + require.NotNil(t, repo.updated) + require.Equal(t, PlatformAnthropic, repo.updated.Platform) + require.False(t, repo.updated.AllowMessagesDispatch) + require.Empty(t, repo.updated.DefaultMappedModel) + require.Equal(t, OpenAIMessagesDispatchModelConfig{}, repo.updated.MessagesDispatchModelConfig) +} + func TestAdminService_ListGroups_WithSearch(t *testing.T) { // 测试: // 1. search 参数正常传递到 repository 层 @@ -258,7 +389,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) { } svc := &adminServiceImpl{groupRepo: repo} - groups, total, err := svc.ListGroups(context.Background(), 1, 20, "", "", "alpha", nil) + groups, total, err := svc.ListGroups(context.Background(), 1, 20, "", "", "alpha", nil, "", "") require.NoError(t, err) require.Equal(t, int64(1), total) require.Equal(t, []Group{{ID: 1, Name: "alpha"}}, groups) @@ -276,7 +407,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) { } svc := &adminServiceImpl{groupRepo: repo} - groups, total, err := svc.ListGroups(context.Background(), 2, 10, "", "", "", nil) + groups, total, err := svc.ListGroups(context.Background(), 2, 10, "", "", "", nil, "", "") require.NoError(t, err) require.Empty(t, groups) require.Equal(t, int64(0), total) @@ -295,7 +426,7 @@ func TestAdminService_ListGroups_WithSearch(t *testing.T) { } svc := &adminServiceImpl{groupRepo: repo} - groups, total, err := svc.ListGroups(context.Background(), 3, 50, PlatformAntigravity, StatusActive, "beta", &isExclusive) + groups, total, err := svc.ListGroups(context.Background(), 3, 50, PlatformAntigravity, StatusActive, "beta", &isExclusive, "", "") require.NoError(t, err) require.Equal(t, int64(42), total) require.Equal(t, []Group{{ID: 2, Name: "beta"}}, groups) diff --git a/backend/internal/service/admin_service_list_users_test.go b/backend/internal/service/admin_service_list_users_test.go index 37f348dfbd4a75d97143e2fe32454124e097b564..ceeb52c2944c830d82a071fecae79f4a00719ad2 100644 --- a/backend/internal/service/admin_service_list_users_test.go +++ b/backend/internal/service/admin_service_list_users_test.go @@ -13,11 +13,13 @@ import ( type userRepoStubForListUsers struct { userRepoStub - users []User - err error + users []User + err error + listWithFiltersParams pagination.PaginationParams } func (s *userRepoStubForListUsers) ListWithFilters(_ context.Context, params pagination.PaginationParams, _ UserListFilters) ([]User, *pagination.PaginationResult, error) { + s.listWithFiltersParams = params if s.err != nil { return nil, nil, s.err } @@ -103,7 +105,7 @@ func TestAdminService_ListUsers_BatchRateFallbackToSingle(t *testing.T) { userGroupRateRepo: rateRepo, } - users, total, err := svc.ListUsers(context.Background(), 1, 20, UserListFilters{}) + users, total, err := svc.ListUsers(context.Background(), 1, 20, UserListFilters{}, "", "") require.NoError(t, err) require.Equal(t, int64(2), total) require.Len(t, users, 2) @@ -112,3 +114,19 @@ func TestAdminService_ListUsers_BatchRateFallbackToSingle(t *testing.T) { require.Equal(t, 1.1, users[0].GroupRates[11]) require.Equal(t, 2.2, users[1].GroupRates[22]) } + +func TestAdminService_ListUsers_PassesSortParams(t *testing.T) { + userRepo := &userRepoStubForListUsers{ + users: []User{{ID: 1, Email: "a@example.com"}}, + } + svc := &adminServiceImpl{userRepo: userRepo} + + _, _, err := svc.ListUsers(context.Background(), 2, 50, UserListFilters{}, "email", "ASC") + require.NoError(t, err) + require.Equal(t, pagination.PaginationParams{ + Page: 2, + PageSize: 50, + SortBy: "email", + SortOrder: "ASC", + }, userRepo.listWithFiltersParams) +} diff --git a/backend/internal/service/admin_service_search_test.go b/backend/internal/service/admin_service_search_test.go index eb213e6af6569b11b364abbe7b22356efc29affa..595e99e344f227e14a7e13e3a3e23da77d10ad46 100644 --- a/backend/internal/service/admin_service_search_test.go +++ b/backend/internal/service/admin_service_search_test.go @@ -170,13 +170,13 @@ func TestAdminService_ListAccounts_WithSearch(t *testing.T) { } svc := &adminServiceImpl{accountRepo: repo} - accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformGemini, AccountTypeOAuth, StatusActive, "acc", 0, "") + accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformGemini, AccountTypeOAuth, StatusActive, "acc", 0, "", "name", "ASC") require.NoError(t, err) require.Equal(t, int64(10), total) require.Equal(t, []Account{{ID: 1, Name: "acc"}}, accounts) require.Equal(t, 1, repo.listWithFiltersCalls) - require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20}, repo.listWithFiltersParams) + require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20, SortBy: "name", SortOrder: "ASC"}, repo.listWithFiltersParams) require.Equal(t, PlatformGemini, repo.listWithFiltersPlatform) require.Equal(t, AccountTypeOAuth, repo.listWithFiltersType) require.Equal(t, StatusActive, repo.listWithFiltersStatus) @@ -192,7 +192,7 @@ func TestAdminService_ListAccounts_WithPrivacyMode(t *testing.T) { } svc := &adminServiceImpl{accountRepo: repo} - accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, StatusActive, "acc2", 0, PrivacyModeCFBlocked) + accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, StatusActive, "acc2", 0, PrivacyModeCFBlocked, "", "") require.NoError(t, err) require.Equal(t, int64(1), total) require.Equal(t, []Account{{ID: 2, Name: "acc2"}}, accounts) @@ -208,13 +208,13 @@ func TestAdminService_ListProxies_WithSearch(t *testing.T) { } svc := &adminServiceImpl{proxyRepo: repo} - proxies, total, err := svc.ListProxies(context.Background(), 3, 50, "http", StatusActive, "p1") + proxies, total, err := svc.ListProxies(context.Background(), 3, 50, "http", StatusActive, "p1", "name", "ASC") require.NoError(t, err) require.Equal(t, int64(7), total) require.Equal(t, []Proxy{{ID: 2, Name: "p1"}}, proxies) require.Equal(t, 1, repo.listWithFiltersCalls) - require.Equal(t, pagination.PaginationParams{Page: 3, PageSize: 50}, repo.listWithFiltersParams) + require.Equal(t, pagination.PaginationParams{Page: 3, PageSize: 50, SortBy: "name", SortOrder: "ASC"}, repo.listWithFiltersParams) require.Equal(t, "http", repo.listWithFiltersProtocol) require.Equal(t, StatusActive, repo.listWithFiltersStatus) require.Equal(t, "p1", repo.listWithFiltersSearch) @@ -229,13 +229,13 @@ func TestAdminService_ListProxiesWithAccountCount_WithSearch(t *testing.T) { } svc := &adminServiceImpl{proxyRepo: repo} - proxies, total, err := svc.ListProxiesWithAccountCount(context.Background(), 2, 10, "socks5", StatusDisabled, "p2") + proxies, total, err := svc.ListProxiesWithAccountCount(context.Background(), 2, 10, "socks5", StatusDisabled, "p2", "account_count", "DESC") require.NoError(t, err) require.Equal(t, int64(9), total) require.Equal(t, []ProxyWithAccountCount{{Proxy: Proxy{ID: 3, Name: "p2"}, AccountCount: 5}}, proxies) require.Equal(t, 1, repo.listWithFiltersAndAccountCountCalls) - require.Equal(t, pagination.PaginationParams{Page: 2, PageSize: 10}, repo.listWithFiltersAndAccountCountParams) + require.Equal(t, pagination.PaginationParams{Page: 2, PageSize: 10, SortBy: "account_count", SortOrder: "DESC"}, repo.listWithFiltersAndAccountCountParams) require.Equal(t, "socks5", repo.listWithFiltersAndAccountCountProtocol) require.Equal(t, StatusDisabled, repo.listWithFiltersAndAccountCountStatus) require.Equal(t, "p2", repo.listWithFiltersAndAccountCountSearch) @@ -250,13 +250,13 @@ func TestAdminService_ListRedeemCodes_WithSearch(t *testing.T) { } svc := &adminServiceImpl{redeemCodeRepo: repo} - codes, total, err := svc.ListRedeemCodes(context.Background(), 1, 20, RedeemTypeBalance, StatusUnused, "ABC") + codes, total, err := svc.ListRedeemCodes(context.Background(), 1, 20, RedeemTypeBalance, StatusUnused, "ABC", "value", "ASC") require.NoError(t, err) require.Equal(t, int64(3), total) require.Equal(t, []RedeemCode{{ID: 4, Code: "ABC"}}, codes) require.Equal(t, 1, repo.listWithFiltersCalls) - require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20}, repo.listWithFiltersParams) + require.Equal(t, pagination.PaginationParams{Page: 1, PageSize: 20, SortBy: "value", SortOrder: "ASC"}, repo.listWithFiltersParams) require.Equal(t, RedeemTypeBalance, repo.listWithFiltersType) require.Equal(t, StatusUnused, repo.listWithFiltersStatus) require.Equal(t, "ABC", repo.listWithFiltersSearch) diff --git a/backend/internal/service/api_key_auth_cache.go b/backend/internal/service/api_key_auth_cache.go index ad6ba0e930851c229dcef3935b3df9a161015b65..c2e96df13ae856e4ddc180eefc0d9f817e8119b6 100644 --- a/backend/internal/service/api_key_auth_cache.go +++ b/backend/internal/service/api_key_auth_cache.go @@ -4,6 +4,7 @@ import "time" // APIKeyAuthSnapshot API Key 认证缓存快照(仅包含认证所需字段) type APIKeyAuthSnapshot struct { + Version int `json:"version"` APIKeyID int64 `json:"api_key_id"` UserID int64 `json:"user_id"` GroupID *int64 `json:"group_id,omitempty"` @@ -63,8 +64,9 @@ type APIKeyAuthGroupSnapshot struct { SupportedModelScopes []string `json:"supported_model_scopes,omitempty"` // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch bool `json:"allow_messages_dispatch"` - DefaultMappedModel string `json:"default_mapped_model,omitempty"` + AllowMessagesDispatch bool `json:"allow_messages_dispatch"` + DefaultMappedModel string `json:"default_mapped_model,omitempty"` + MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"` } // APIKeyAuthCacheEntry 缓存条目,支持负缓存 diff --git a/backend/internal/service/api_key_auth_cache_impl.go b/backend/internal/service/api_key_auth_cache_impl.go index 64a70e8cf4a8639017b1508bcf5e8276a428658a..8069ed4fe2877d393941264aaa98096c299b7769 100644 --- a/backend/internal/service/api_key_auth_cache_impl.go +++ b/backend/internal/service/api_key_auth_cache_impl.go @@ -13,6 +13,8 @@ import ( "github.com/dgraph-io/ristretto" ) +const apiKeyAuthSnapshotVersion = 3 + type apiKeyAuthCacheConfig struct { l1Size int l1TTL time.Duration @@ -192,6 +194,9 @@ func (s *APIKeyService) applyAuthCacheEntry(key string, entry *APIKeyAuthCacheEn if entry.Snapshot == nil { return nil, false, nil } + if entry.Snapshot.Version != apiKeyAuthSnapshotVersion { + return nil, false, nil + } return s.snapshotToAPIKey(key, entry.Snapshot), true, nil } @@ -200,6 +205,7 @@ func (s *APIKeyService) snapshotFromAPIKey(apiKey *APIKey) *APIKeyAuthSnapshot { return nil } snapshot := &APIKeyAuthSnapshot{ + Version: apiKeyAuthSnapshotVersion, APIKeyID: apiKey.ID, UserID: apiKey.UserID, GroupID: apiKey.GroupID, @@ -243,6 +249,7 @@ func (s *APIKeyService) snapshotFromAPIKey(apiKey *APIKey) *APIKeyAuthSnapshot { SupportedModelScopes: apiKey.Group.SupportedModelScopes, AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch, DefaultMappedModel: apiKey.Group.DefaultMappedModel, + MessagesDispatchModelConfig: apiKey.Group.MessagesDispatchModelConfig, } } return snapshot @@ -298,6 +305,7 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho SupportedModelScopes: snapshot.Group.SupportedModelScopes, AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch, DefaultMappedModel: snapshot.Group.DefaultMappedModel, + MessagesDispatchModelConfig: snapshot.Group.MessagesDispatchModelConfig, } } s.compileAPIKeyIPRules(apiKey) diff --git a/backend/internal/service/api_key_service_cache_test.go b/backend/internal/service/api_key_service_cache_test.go index 357f8deff7a89dff64c026a09705402384f6da8d..3c2f7dbb5c59edc9a87f7c2c9ab39733c36c6cdf 100644 --- a/backend/internal/service/api_key_service_cache_test.go +++ b/backend/internal/service/api_key_service_cache_test.go @@ -188,6 +188,7 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) { groupID := int64(9) cacheEntry := &APIKeyAuthCacheEntry{ Snapshot: &APIKeyAuthSnapshot{ + Version: apiKeyAuthSnapshotVersion, APIKeyID: 1, UserID: 2, GroupID: &groupID, @@ -226,6 +227,129 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) { require.Equal(t, map[string][]int64{"claude-opus-*": {1, 2}}, apiKey.Group.ModelRouting) } +func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t *testing.T) { + svc := NewAPIKeyService(nil, nil, nil, nil, nil, nil, &config.Config{}) + groupID := int64(9) + apiKey := &APIKey{ + ID: 1, + UserID: 2, + GroupID: &groupID, + Key: "k-roundtrip", + Status: StatusActive, + User: &User{ + ID: 2, + Status: StatusActive, + Role: RoleUser, + Balance: 10, + Concurrency: 3, + }, + Group: &Group{ + ID: groupID, + Name: "openai", + Platform: PlatformOpenAI, + Status: StatusActive, + SubscriptionType: SubscriptionTypeStandard, + RateMultiplier: 1, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4-nano", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: "gpt-5.4-mini", + ExactModelMappings: map[string]string{ + "claude-sonnet-4.5": "gpt-5.4-nano", + }, + }, + }, + } + + snapshot := svc.snapshotFromAPIKey(apiKey) + roundTrip := svc.snapshotToAPIKey(apiKey.Key, snapshot) + + require.NotNil(t, roundTrip) + require.NotNil(t, roundTrip.Group) + require.Equal(t, apiKey.Group.MessagesDispatchModelConfig, roundTrip.Group.MessagesDispatchModelConfig) +} + +func TestAPIKeyService_GetByKey_IgnoresLegacyAuthCacheSnapshotWithoutMessagesDispatchConfig(t *testing.T) { + cache := &authCacheStub{} + var repoCalls int32 + repo := &authRepoStub{ + getByKeyForAuth: func(ctx context.Context, key string) (*APIKey, error) { + atomic.AddInt32(&repoCalls, 1) + groupID := int64(9) + return &APIKey{ + ID: 1, + UserID: 2, + GroupID: &groupID, + Status: StatusActive, + User: &User{ + ID: 2, + Status: StatusActive, + Role: RoleUser, + Balance: 10, + Concurrency: 3, + }, + Group: &Group{ + ID: groupID, + Name: "openai", + Platform: PlatformOpenAI, + Status: StatusActive, + Hydrated: true, + SubscriptionType: SubscriptionTypeStandard, + RateMultiplier: 1, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: "gpt-5.4-nano", + }, + }, + }, nil + }, + } + cfg := &config.Config{ + APIKeyAuth: config.APIKeyAuthCacheConfig{ + L2TTLSeconds: 60, + }, + } + svc := NewAPIKeyService(repo, nil, nil, nil, nil, cache, cfg) + + groupID := int64(9) + cache.getAuthCache = func(ctx context.Context, key string) (*APIKeyAuthCacheEntry, error) { + return &APIKeyAuthCacheEntry{ + Snapshot: &APIKeyAuthSnapshot{ + APIKeyID: 1, + UserID: 2, + GroupID: &groupID, + Status: StatusActive, + User: APIKeyAuthUserSnapshot{ + ID: 2, + Status: StatusActive, + Role: RoleUser, + Balance: 10, + Concurrency: 3, + }, + Group: &APIKeyAuthGroupSnapshot{ + ID: groupID, + Name: "openai", + Platform: PlatformOpenAI, + Status: StatusActive, + SubscriptionType: SubscriptionTypeStandard, + RateMultiplier: 1, + AllowMessagesDispatch: true, + DefaultMappedModel: "gpt-5.4", + }, + }, + }, nil + } + + apiKey, err := svc.GetByKey(context.Background(), "k-legacy") + require.NoError(t, err) + require.Equal(t, int32(1), atomic.LoadInt32(&repoCalls)) + require.NotNil(t, apiKey.Group) + require.Equal(t, "gpt-5.4-nano", apiKey.Group.MessagesDispatchModelConfig.OpusMappedModel) +} + func TestAPIKeyService_GetByKey_NegativeCache(t *testing.T) { cache := &authCacheStub{} repo := &authRepoStub{ diff --git a/backend/internal/service/auth_service.go b/backend/internal/service/auth_service.go index fcf39e1afd55cb8cdfb34b080e9d2e833a786a72..847f94a8edccbf73be5abc5a5169284952d81c3f 100644 --- a/backend/internal/service/auth_service.go +++ b/backend/internal/service/auth_service.go @@ -833,7 +833,8 @@ func randomHexString(byteLength int) (string, error) { func isReservedEmail(email string) bool { normalized := strings.ToLower(strings.TrimSpace(email)) - return strings.HasSuffix(normalized, LinuxDoConnectSyntheticEmailDomain) + return strings.HasSuffix(normalized, LinuxDoConnectSyntheticEmailDomain) || + strings.HasSuffix(normalized, OIDCConnectSyntheticEmailDomain) } // GenerateToken 生成JWT access token diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index 92be3e06cfbfb6dc075ffb56212157dc24081972..68d7da3b45ffba61856b4c605ec583540852f6ed 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -71,6 +71,9 @@ const ( // LinuxDoConnectSyntheticEmailDomain 是 LinuxDo Connect 用户的合成邮箱后缀(RFC 保留域名)。 const LinuxDoConnectSyntheticEmailDomain = "@linuxdo-connect.invalid" +// OIDCConnectSyntheticEmailDomain 是 OIDC 用户的合成邮箱后缀(RFC 保留域名)。 +const OIDCConnectSyntheticEmailDomain = "@oidc-connect.invalid" + // Setting keys const ( // 注册设置 @@ -105,6 +108,30 @@ const ( SettingKeyLinuxDoConnectClientSecret = "linuxdo_connect_client_secret" SettingKeyLinuxDoConnectRedirectURL = "linuxdo_connect_redirect_url" + // Generic OIDC OAuth 登录设置 + SettingKeyOIDCConnectEnabled = "oidc_connect_enabled" + SettingKeyOIDCConnectProviderName = "oidc_connect_provider_name" + SettingKeyOIDCConnectClientID = "oidc_connect_client_id" + SettingKeyOIDCConnectClientSecret = "oidc_connect_client_secret" + SettingKeyOIDCConnectIssuerURL = "oidc_connect_issuer_url" + SettingKeyOIDCConnectDiscoveryURL = "oidc_connect_discovery_url" + SettingKeyOIDCConnectAuthorizeURL = "oidc_connect_authorize_url" + SettingKeyOIDCConnectTokenURL = "oidc_connect_token_url" + SettingKeyOIDCConnectUserInfoURL = "oidc_connect_userinfo_url" + SettingKeyOIDCConnectJWKSURL = "oidc_connect_jwks_url" + SettingKeyOIDCConnectScopes = "oidc_connect_scopes" + SettingKeyOIDCConnectRedirectURL = "oidc_connect_redirect_url" + SettingKeyOIDCConnectFrontendRedirectURL = "oidc_connect_frontend_redirect_url" + SettingKeyOIDCConnectTokenAuthMethod = "oidc_connect_token_auth_method" + SettingKeyOIDCConnectUsePKCE = "oidc_connect_use_pkce" + SettingKeyOIDCConnectValidateIDToken = "oidc_connect_validate_id_token" + SettingKeyOIDCConnectAllowedSigningAlgs = "oidc_connect_allowed_signing_algs" + SettingKeyOIDCConnectClockSkewSeconds = "oidc_connect_clock_skew_seconds" + SettingKeyOIDCConnectRequireEmailVerified = "oidc_connect_require_email_verified" + SettingKeyOIDCConnectUserInfoEmailPath = "oidc_connect_userinfo_email_path" + SettingKeyOIDCConnectUserInfoIDPath = "oidc_connect_userinfo_id_path" + SettingKeyOIDCConnectUserInfoUsernamePath = "oidc_connect_userinfo_username_path" + // OEM设置 SettingKeySiteName = "site_name" // 网站名称 SettingKeySiteLogo = "site_logo" // 网站Logo (base64) @@ -116,6 +143,8 @@ const ( SettingKeyHideCcsImportButton = "hide_ccs_import_button" // 是否隐藏 API Keys 页面的导入 CCS 按钮 SettingKeyPurchaseSubscriptionEnabled = "purchase_subscription_enabled" // 是否展示"购买订阅"页面入口 SettingKeyPurchaseSubscriptionURL = "purchase_subscription_url" // "购买订阅"页面 URL(作为 iframe src) + SettingKeyTableDefaultPageSize = "table_default_page_size" // 表格默认每页条数 + SettingKeyTablePageSizeOptions = "table_page_size_options" // 表格可选每页条数(JSON 数组) SettingKeyCustomMenuItems = "custom_menu_items" // 自定义菜单项(JSON 数组) SettingKeyCustomEndpoints = "custom_endpoints" // 自定义端点列表(JSON 数组) diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index a473364916c483fc9ef2ac416292803783a665da..8b0bdc2a58f6d68359cefd41c7587726c00ffcf0 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -1192,12 +1192,20 @@ func (s *GatewayService) SelectAccountForModelWithExclusions(ctx context.Context // anthropic/gemini 分组支持混合调度(包含启用了 mixed_scheduling 的 antigravity 账户) // 注意:强制平台模式不走混合调度 if (platform == PlatformAnthropic || platform == PlatformGemini) && !hasForcePlatform { - return s.selectAccountWithMixedScheduling(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform) + account, err := s.selectAccountWithMixedScheduling(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform) + if err != nil { + return nil, err + } + return s.hydrateSelectedAccount(ctx, account) } // antigravity 分组、强制平台模式或无分组使用单平台选择 // 注意:强制平台模式也必须遵守分组限制,不再回退到全平台查询 - return s.selectAccountForModelWithPlatform(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform) + account, err := s.selectAccountForModelWithPlatform(ctx, groupID, sessionHash, requestedModel, excludedIDs, platform) + if err != nil { + return nil, err + } + return s.hydrateSelectedAccount(ctx, account) } // SelectAccountWithLoadAwareness selects account with load-awareness and wait plan. @@ -1273,11 +1281,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro localExcluded[account.ID] = struct{}{} // 排除此账号 continue // 重新选择 } - return &AccountSelectionResult{ - Account: account, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil) } // 对于等待计划的情况,也需要先检查会话限制 @@ -1289,26 +1293,20 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if stickyAccountID > 0 && stickyAccountID == account.ID && s.concurrencyService != nil { waitingCount, _ := s.concurrencyService.GetAccountWaitingCount(ctx, account.ID) if waitingCount < cfg.StickySessionMaxWaiting { - return &AccountSelectionResult{ - Account: account, - WaitPlan: &AccountWaitPlan{ - AccountID: account.ID, - MaxConcurrency: account.Concurrency, - Timeout: cfg.StickySessionWaitTimeout, - MaxWaiting: cfg.StickySessionMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{ + AccountID: account.ID, + MaxConcurrency: account.Concurrency, + Timeout: cfg.StickySessionWaitTimeout, + MaxWaiting: cfg.StickySessionMaxWaiting, + }) } } - return &AccountSelectionResult{ - Account: account, - WaitPlan: &AccountWaitPlan{ - AccountID: account.ID, - MaxConcurrency: account.Concurrency, - Timeout: cfg.FallbackWaitTimeout, - MaxWaiting: cfg.FallbackMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{ + AccountID: account.ID, + MaxConcurrency: account.Concurrency, + Timeout: cfg.FallbackWaitTimeout, + MaxWaiting: cfg.FallbackMaxWaiting, + }) } } @@ -1455,11 +1453,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if s.debugModelRoutingEnabled() { logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed sticky hit: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), stickyAccountID) } - return &AccountSelectionResult{ - Account: stickyAccount, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, stickyAccount, true, result.ReleaseFunc, nil) } } @@ -1570,11 +1564,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if s.debugModelRoutingEnabled() { logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed select: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID) } - return &AccountSelectionResult{ - Account: item.account, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, item.account, true, result.ReleaseFunc, nil) } } @@ -1587,15 +1577,12 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if s.debugModelRoutingEnabled() { logger.LegacyPrintf("service.gateway", "[ModelRoutingDebug] routed wait: group_id=%v model=%s session=%s account=%d", derefGroupID(groupID), requestedModel, shortSessionHash(sessionHash), item.account.ID) } - return &AccountSelectionResult{ - Account: item.account, - WaitPlan: &AccountWaitPlan{ - AccountID: item.account.ID, - MaxConcurrency: item.account.Concurrency, - Timeout: cfg.StickySessionWaitTimeout, - MaxWaiting: cfg.StickySessionMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, item.account, false, nil, &AccountWaitPlan{ + AccountID: item.account.ID, + MaxConcurrency: item.account.Concurrency, + Timeout: cfg.StickySessionWaitTimeout, + MaxWaiting: cfg.StickySessionMaxWaiting, + }) } // 所有路由账号会话限制都已满,继续到 Layer 2 回退 } @@ -1631,11 +1618,10 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if !s.checkAndRegisterSession(ctx, account, sessionHash) { result.ReleaseFunc() // 释放槽位,继续到 Layer 2 } else { - return &AccountSelectionResult{ - Account: account, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + if s.cache != nil { + _ = s.cache.RefreshSessionTTL(ctx, derefGroupID(groupID), sessionHash, stickySessionTTL) + } + return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil) } } @@ -1647,15 +1633,12 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro // 会话限制已满,继续到 Layer 2 // Session limit full, continue to Layer 2 } else { - return &AccountSelectionResult{ - Account: account, - WaitPlan: &AccountWaitPlan{ - AccountID: accountID, - MaxConcurrency: account.Concurrency, - Timeout: cfg.StickySessionWaitTimeout, - MaxWaiting: cfg.StickySessionMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{ + AccountID: accountID, + MaxConcurrency: account.Concurrency, + Timeout: cfg.StickySessionWaitTimeout, + MaxWaiting: cfg.StickySessionMaxWaiting, + }) } } } @@ -1714,7 +1697,9 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro loadMap, err := s.concurrencyService.GetAccountsLoadBatch(ctx, accountLoads) if err != nil { - if result, ok := s.tryAcquireByLegacyOrder(ctx, candidates, groupID, sessionHash, preferOAuth); ok { + if result, ok, legacyErr := s.tryAcquireByLegacyOrder(ctx, candidates, groupID, sessionHash, preferOAuth); legacyErr != nil { + return nil, legacyErr + } else if ok { return result, nil } } else { @@ -1753,11 +1738,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if sessionHash != "" && s.cache != nil { _ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, selected.account.ID, stickySessionTTL) } - return &AccountSelectionResult{ - Account: selected.account, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, selected.account, true, result.ReleaseFunc, nil) } } @@ -1780,20 +1761,17 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro if !s.checkAndRegisterSession(ctx, acc, sessionHash) { continue // 会话限制已满,尝试下一个账号 } - return &AccountSelectionResult{ - Account: acc, - WaitPlan: &AccountWaitPlan{ - AccountID: acc.ID, - MaxConcurrency: acc.Concurrency, - Timeout: cfg.FallbackWaitTimeout, - MaxWaiting: cfg.FallbackMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, acc, false, nil, &AccountWaitPlan{ + AccountID: acc.ID, + MaxConcurrency: acc.Concurrency, + Timeout: cfg.FallbackWaitTimeout, + MaxWaiting: cfg.FallbackMaxWaiting, + }) } return nil, ErrNoAvailableAccounts } -func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool) { +func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool, error) { ordered := append([]*Account(nil), candidates...) sortAccountsByPriorityAndLastUsed(ordered, preferOAuth) @@ -1808,15 +1786,15 @@ func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates if sessionHash != "" && s.cache != nil { _ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), sessionHash, acc.ID, stickySessionTTL) } - return &AccountSelectionResult{ - Account: acc, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, true + selection, err := s.newSelectionResult(ctx, acc, true, result.ReleaseFunc, nil) + if err != nil { + return nil, false, err + } + return selection, true, nil } } - return nil, false + return nil, false, nil } func (s *GatewayService) schedulingConfig() config.GatewaySchedulingConfig { @@ -2431,6 +2409,33 @@ func (s *GatewayService) getSchedulableAccount(ctx context.Context, accountID in return s.accountRepo.GetByID(ctx, accountID) } +func (s *GatewayService) hydrateSelectedAccount(ctx context.Context, account *Account) (*Account, error) { + if account == nil || s.schedulerSnapshot == nil { + return account, nil + } + hydrated, err := s.schedulerSnapshot.GetAccount(ctx, account.ID) + if err != nil { + return nil, err + } + if hydrated == nil { + return nil, fmt.Errorf("selected gateway account %d not found during hydration", account.ID) + } + return hydrated, nil +} + +func (s *GatewayService) newSelectionResult(ctx context.Context, account *Account, acquired bool, release func(), waitPlan *AccountWaitPlan) (*AccountSelectionResult, error) { + hydrated, err := s.hydrateSelectedAccount(ctx, account) + if err != nil { + return nil, err + } + return &AccountSelectionResult{ + Account: hydrated, + Acquired: acquired, + ReleaseFunc: release, + WaitPlan: waitPlan, + }, nil +} + // filterByMinPriority 过滤出优先级最小的账号集合 func filterByMinPriority(accounts []accountWithLoad) []accountWithLoad { if len(accounts) == 0 { diff --git a/backend/internal/service/gemini_messages_compat_service.go b/backend/internal/service/gemini_messages_compat_service.go index 32bf21c01f19cd5e21f5aa1a6dce544b25bdc196..5a9490f36d415f585774c66cb4779107eafaa955 100644 --- a/backend/internal/service/gemini_messages_compat_service.go +++ b/backend/internal/service/gemini_messages_compat_service.go @@ -137,7 +137,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForModelWithExclusions(ctx co _ = s.cache.SetSessionAccountID(ctx, derefGroupID(groupID), cacheKey, selected.ID, geminiStickySessionTTL) } - return selected, nil + return s.hydrateSelectedAccount(ctx, selected) } // resolvePlatformAndSchedulingMode 解析目标平台和调度模式。 @@ -416,6 +416,20 @@ func (s *GeminiMessagesCompatService) getSchedulableAccount(ctx context.Context, return s.accountRepo.GetByID(ctx, accountID) } +func (s *GeminiMessagesCompatService) hydrateSelectedAccount(ctx context.Context, account *Account) (*Account, error) { + if account == nil || s.schedulerSnapshot == nil { + return account, nil + } + hydrated, err := s.schedulerSnapshot.GetAccount(ctx, account.ID) + if err != nil { + return nil, err + } + if hydrated == nil { + return nil, fmt.Errorf("selected gemini account %d not found during hydration", account.ID) + } + return hydrated, nil +} + func (s *GeminiMessagesCompatService) listSchedulableAccountsOnce(ctx context.Context, groupID *int64, platform string, hasForcePlatform bool) ([]Account, error) { if s.schedulerSnapshot != nil { accounts, _, err := s.schedulerSnapshot.ListSchedulableAccounts(ctx, groupID, platform, hasForcePlatform) @@ -546,7 +560,7 @@ func (s *GeminiMessagesCompatService) SelectAccountForAIStudioEndpoints(ctx cont if selected == nil { return nil, errors.New("no available Gemini accounts") } - return selected, nil + return s.hydrateSelectedAccount(ctx, selected) } func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Context, account *Account, body []byte) (*ForwardResult, error) { diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go index d59af9e1c09e472736c086db2407832dc8a0fe2b..12262613571e347d00a9ed1f4dfbffbd6649c57a 100644 --- a/backend/internal/service/group.go +++ b/backend/internal/service/group.go @@ -3,8 +3,12 @@ package service import ( "strings" "time" + + "github.com/Wei-Shaw/sub2api/internal/domain" ) +type OpenAIMessagesDispatchModelConfig = domain.OpenAIMessagesDispatchModelConfig + type Group struct { ID int64 Name string @@ -49,10 +53,11 @@ type Group struct { SortOrder int // OpenAI Messages 调度配置(仅 openai 平台使用) - AllowMessagesDispatch bool - RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini) - RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini) - DefaultMappedModel string + AllowMessagesDispatch bool + RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini) + RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini) + DefaultMappedModel string + MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig CreatedAt time.Time UpdatedAt time.Time diff --git a/backend/internal/service/openai_codex_instructions_template.go b/backend/internal/service/openai_codex_instructions_template.go new file mode 100644 index 0000000000000000000000000000000000000000..5588c73cbbaa5eb8b548ceaa7b4272e36a8c3f7d --- /dev/null +++ b/backend/internal/service/openai_codex_instructions_template.go @@ -0,0 +1,55 @@ +package service + +import ( + "bytes" + "fmt" + "strings" + "text/template" +) + +type forcedCodexInstructionsTemplateData struct { + ExistingInstructions string + OriginalModel string + NormalizedModel string + BillingModel string + UpstreamModel string +} + +func applyForcedCodexInstructionsTemplate( + reqBody map[string]any, + templateText string, + data forcedCodexInstructionsTemplateData, +) (bool, error) { + rendered, err := renderForcedCodexInstructionsTemplate(templateText, data) + if err != nil { + return false, err + } + if rendered == "" { + return false, nil + } + + existing, _ := reqBody["instructions"].(string) + if strings.TrimSpace(existing) == rendered { + return false, nil + } + + reqBody["instructions"] = rendered + return true, nil +} + +func renderForcedCodexInstructionsTemplate( + templateText string, + data forcedCodexInstructionsTemplateData, +) (string, error) { + tmpl, err := template.New("forced_codex_instructions").Option("missingkey=zero").Parse(templateText) + if err != nil { + return "", fmt.Errorf("parse forced codex instructions template: %w", err) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, data); err != nil { + return "", fmt.Errorf("render forced codex instructions template: %w", err) + } + + return strings.TrimSpace(buf.String()), nil +} diff --git a/backend/internal/service/openai_compat_model_test.go b/backend/internal/service/openai_compat_model_test.go index 32c646d490b57d5d501f1db1689c597b1f68bd8e..4396c15fd0b2c239202533fcb10fd4e8a43487e4 100644 --- a/backend/internal/service/openai_compat_model_test.go +++ b/backend/internal/service/openai_compat_model_test.go @@ -6,9 +6,12 @@ import ( "io" "net/http" "net/http/httptest" + "os" + "path/filepath" "strings" "testing" + "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" @@ -127,3 +130,101 @@ func TestForwardAsAnthropic_NormalizesRoutingAndEffortForGpt54XHigh(t *testing.T t.Logf("upstream body: %s", string(upstream.lastBody)) t.Logf("response body: %s", rec.Body.String()) } + +func TestForwardAsAnthropic_ForcedCodexInstructionsTemplatePrependsRenderedInstructions(t *testing.T) { + t.Parallel() + gin.SetMode(gin.TestMode) + + templateDir := t.TempDir() + templatePath := filepath.Join(templateDir, "codex-instructions.md.tmpl") + require.NoError(t, os.WriteFile(templatePath, []byte("server-prefix\n\n{{ .ExistingInstructions }}"), 0o644)) + + rec := httptest.NewRecorder() + c, _ := gin.CreateTestContext(rec) + body := []byte(`{"model":"gpt-5.4","max_tokens":16,"system":"client-system","messages":[{"role":"user","content":"hello"}],"stream":false}`) + c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", bytes.NewReader(body)) + c.Request.Header.Set("Content-Type", "application/json") + + upstreamBody := strings.Join([]string{ + `data: {"type":"response.completed","response":{"id":"resp_1","object":"response","model":"gpt-5.4","status":"completed","output":[{"type":"message","id":"msg_1","role":"assistant","status":"completed","content":[{"type":"output_text","text":"ok"}]}],"usage":{"input_tokens":5,"output_tokens":2,"total_tokens":7}}}`, + "", + "data: [DONE]", + "", + }, "\n") + upstream := &httpUpstreamRecorder{resp: &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/event-stream"}, "x-request-id": []string{"rid_forced"}}, + Body: io.NopCloser(strings.NewReader(upstreamBody)), + }} + + svc := &OpenAIGatewayService{ + cfg: &config.Config{Gateway: config.GatewayConfig{ + ForcedCodexInstructionsTemplateFile: templatePath, + ForcedCodexInstructionsTemplate: "server-prefix\n\n{{ .ExistingInstructions }}", + }}, + httpUpstream: upstream, + } + account := &Account{ + ID: 1, + Name: "openai-oauth", + Platform: PlatformOpenAI, + Type: AccountTypeOAuth, + Concurrency: 1, + Credentials: map[string]any{ + "access_token": "oauth-token", + "chatgpt_account_id": "chatgpt-acc", + }, + } + + result, err := svc.ForwardAsAnthropic(context.Background(), c, account, body, "", "gpt-5.1") + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, "server-prefix\n\nclient-system", gjson.GetBytes(upstream.lastBody, "instructions").String()) +} + +func TestForwardAsAnthropic_ForcedCodexInstructionsTemplateUsesCachedTemplateContent(t *testing.T) { + t.Parallel() + gin.SetMode(gin.TestMode) + + rec := httptest.NewRecorder() + c, _ := gin.CreateTestContext(rec) + body := []byte(`{"model":"gpt-5.4","max_tokens":16,"system":"client-system","messages":[{"role":"user","content":"hello"}],"stream":false}`) + c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", bytes.NewReader(body)) + c.Request.Header.Set("Content-Type", "application/json") + + upstreamBody := strings.Join([]string{ + `data: {"type":"response.completed","response":{"id":"resp_1","object":"response","model":"gpt-5.4","status":"completed","output":[{"type":"message","id":"msg_1","role":"assistant","status":"completed","content":[{"type":"output_text","text":"ok"}]}],"usage":{"input_tokens":5,"output_tokens":2,"total_tokens":7}}}`, + "", + "data: [DONE]", + "", + }, "\n") + upstream := &httpUpstreamRecorder{resp: &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/event-stream"}, "x-request-id": []string{"rid_forced_cached"}}, + Body: io.NopCloser(strings.NewReader(upstreamBody)), + }} + + svc := &OpenAIGatewayService{ + cfg: &config.Config{Gateway: config.GatewayConfig{ + ForcedCodexInstructionsTemplateFile: "/path/that/should/not/be/read.tmpl", + ForcedCodexInstructionsTemplate: "cached-prefix\n\n{{ .ExistingInstructions }}", + }}, + httpUpstream: upstream, + } + account := &Account{ + ID: 1, + Name: "openai-oauth", + Platform: PlatformOpenAI, + Type: AccountTypeOAuth, + Concurrency: 1, + Credentials: map[string]any{ + "access_token": "oauth-token", + "chatgpt_account_id": "chatgpt-acc", + }, + } + + result, err := svc.ForwardAsAnthropic(context.Background(), c, account, body, "", "gpt-5.1") + require.NoError(t, err) + require.NotNil(t, result) + require.Equal(t, "cached-prefix\n\nclient-system", gjson.GetBytes(upstream.lastBody, "instructions").String()) +} diff --git a/backend/internal/service/openai_gateway_messages.go b/backend/internal/service/openai_gateway_messages.go index 6f53928b7d2da6ada0f8c92c2a4acc82f7015350..7a4862d33510dff9da81666a98225c828ab07ff6 100644 --- a/backend/internal/service/openai_gateway_messages.go +++ b/backend/internal/service/openai_gateway_messages.go @@ -86,6 +86,24 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic( return nil, fmt.Errorf("unmarshal for codex transform: %w", err) } codexResult := applyCodexOAuthTransform(reqBody, false, false) + forcedTemplateText := "" + if s.cfg != nil { + forcedTemplateText = s.cfg.Gateway.ForcedCodexInstructionsTemplate + } + templateUpstreamModel := upstreamModel + if codexResult.NormalizedModel != "" { + templateUpstreamModel = codexResult.NormalizedModel + } + existingInstructions, _ := reqBody["instructions"].(string) + if _, err := applyForcedCodexInstructionsTemplate(reqBody, forcedTemplateText, forcedCodexInstructionsTemplateData{ + ExistingInstructions: strings.TrimSpace(existingInstructions), + OriginalModel: originalModel, + NormalizedModel: normalizedModel, + BillingModel: billingModel, + UpstreamModel: templateUpstreamModel, + }); err != nil { + return nil, err + } if codexResult.NormalizedModel != "" { upstreamModel = codexResult.NormalizedModel } diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index 2623d773770432e4d52c1cacc975d54a8502a5a6..dbc538692036f25d4a59b0ccc7456cc636c9dc72 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -1243,7 +1243,7 @@ func (s *OpenAIGatewayService) selectAccountForModelWithExclusions(ctx context.C _ = s.setStickySessionAccountID(ctx, groupID, sessionHash, selected.ID, openaiStickySessionTTL) } - return selected, nil + return s.hydrateSelectedAccount(ctx, selected) } // tryStickySessionHit 尝试从粘性会话获取账号。 @@ -1408,35 +1408,25 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex } result, err := s.tryAcquireAccountSlot(ctx, account.ID, account.Concurrency) if err == nil && result.Acquired { - return &AccountSelectionResult{ - Account: account, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil) } if stickyAccountID > 0 && stickyAccountID == account.ID && s.concurrencyService != nil { waitingCount, _ := s.concurrencyService.GetAccountWaitingCount(ctx, account.ID) if waitingCount < cfg.StickySessionMaxWaiting { - return &AccountSelectionResult{ - Account: account, - WaitPlan: &AccountWaitPlan{ - AccountID: account.ID, - MaxConcurrency: account.Concurrency, - Timeout: cfg.StickySessionWaitTimeout, - MaxWaiting: cfg.StickySessionMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{ + AccountID: account.ID, + MaxConcurrency: account.Concurrency, + Timeout: cfg.StickySessionWaitTimeout, + MaxWaiting: cfg.StickySessionMaxWaiting, + }) } } - return &AccountSelectionResult{ - Account: account, - WaitPlan: &AccountWaitPlan{ - AccountID: account.ID, - MaxConcurrency: account.Concurrency, - Timeout: cfg.FallbackWaitTimeout, - MaxWaiting: cfg.FallbackMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{ + AccountID: account.ID, + MaxConcurrency: account.Concurrency, + Timeout: cfg.FallbackWaitTimeout, + MaxWaiting: cfg.FallbackMaxWaiting, + }) } accounts, err := s.listSchedulableAccounts(ctx, groupID) @@ -1476,24 +1466,17 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex result, err := s.tryAcquireAccountSlot(ctx, accountID, account.Concurrency) if err == nil && result.Acquired { _ = s.refreshStickySessionTTL(ctx, groupID, sessionHash, openaiStickySessionTTL) - return &AccountSelectionResult{ - Account: account, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, account, true, result.ReleaseFunc, nil) } waitingCount, _ := s.concurrencyService.GetAccountWaitingCount(ctx, accountID) if waitingCount < cfg.StickySessionMaxWaiting { - return &AccountSelectionResult{ - Account: account, - WaitPlan: &AccountWaitPlan{ - AccountID: accountID, - MaxConcurrency: account.Concurrency, - Timeout: cfg.StickySessionWaitTimeout, - MaxWaiting: cfg.StickySessionMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, account, false, nil, &AccountWaitPlan{ + AccountID: accountID, + MaxConcurrency: account.Concurrency, + Timeout: cfg.StickySessionWaitTimeout, + MaxWaiting: cfg.StickySessionMaxWaiting, + }) } } } @@ -1552,11 +1535,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex if sessionHash != "" { _ = s.setStickySessionAccountID(ctx, groupID, sessionHash, fresh.ID, openaiStickySessionTTL) } - return &AccountSelectionResult{ - Account: fresh, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, fresh, true, result.ReleaseFunc, nil) } } } else { @@ -1609,11 +1588,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex if sessionHash != "" { _ = s.setStickySessionAccountID(ctx, groupID, sessionHash, fresh.ID, openaiStickySessionTTL) } - return &AccountSelectionResult{ - Account: fresh, - Acquired: true, - ReleaseFunc: result.ReleaseFunc, - }, nil + return s.newSelectionResult(ctx, fresh, true, result.ReleaseFunc, nil) } } } @@ -1629,15 +1604,12 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex if needsUpstreamCheck && s.isUpstreamModelRestrictedByChannel(ctx, *groupID, fresh, requestedModel) { continue } - return &AccountSelectionResult{ - Account: fresh, - WaitPlan: &AccountWaitPlan{ - AccountID: fresh.ID, - MaxConcurrency: fresh.Concurrency, - Timeout: cfg.FallbackWaitTimeout, - MaxWaiting: cfg.FallbackMaxWaiting, - }, - }, nil + return s.newSelectionResult(ctx, fresh, false, nil, &AccountWaitPlan{ + AccountID: fresh.ID, + MaxConcurrency: fresh.Concurrency, + Timeout: cfg.FallbackWaitTimeout, + MaxWaiting: cfg.FallbackMaxWaiting, + }) } return nil, ErrNoAvailableAccounts @@ -1732,6 +1704,33 @@ func (s *OpenAIGatewayService) getSchedulableAccount(ctx context.Context, accoun return account, nil } +func (s *OpenAIGatewayService) hydrateSelectedAccount(ctx context.Context, account *Account) (*Account, error) { + if account == nil || s.schedulerSnapshot == nil { + return account, nil + } + hydrated, err := s.schedulerSnapshot.GetAccount(ctx, account.ID) + if err != nil { + return nil, err + } + if hydrated == nil { + return nil, fmt.Errorf("selected openai account %d not found during hydration", account.ID) + } + return hydrated, nil +} + +func (s *OpenAIGatewayService) newSelectionResult(ctx context.Context, account *Account, acquired bool, release func(), waitPlan *AccountWaitPlan) (*AccountSelectionResult, error) { + hydrated, err := s.hydrateSelectedAccount(ctx, account) + if err != nil { + return nil, err + } + return &AccountSelectionResult{ + Account: hydrated, + Acquired: acquired, + ReleaseFunc: release, + WaitPlan: waitPlan, + }, nil +} + func (s *OpenAIGatewayService) schedulingConfig() config.GatewaySchedulingConfig { if s.cfg != nil { return s.cfg.Gateway.Scheduling diff --git a/backend/internal/service/openai_messages_dispatch.go b/backend/internal/service/openai_messages_dispatch.go new file mode 100644 index 0000000000000000000000000000000000000000..f2c1ad3c1baffe3eafafed4b4c6b9e83bf1a73a6 --- /dev/null +++ b/backend/internal/service/openai_messages_dispatch.go @@ -0,0 +1,100 @@ +package service + +import "strings" + +const ( + defaultOpenAIMessagesDispatchOpusMappedModel = "gpt-5.4" + defaultOpenAIMessagesDispatchSonnetMappedModel = "gpt-5.3-codex" + defaultOpenAIMessagesDispatchHaikuMappedModel = "gpt-5.4-mini" +) + +func normalizeOpenAIMessagesDispatchMappedModel(model string) string { + model = NormalizeOpenAICompatRequestedModel(strings.TrimSpace(model)) + return strings.TrimSpace(model) +} + +func normalizeOpenAIMessagesDispatchModelConfig(cfg OpenAIMessagesDispatchModelConfig) OpenAIMessagesDispatchModelConfig { + out := OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: normalizeOpenAIMessagesDispatchMappedModel(cfg.OpusMappedModel), + SonnetMappedModel: normalizeOpenAIMessagesDispatchMappedModel(cfg.SonnetMappedModel), + HaikuMappedModel: normalizeOpenAIMessagesDispatchMappedModel(cfg.HaikuMappedModel), + } + + if len(cfg.ExactModelMappings) > 0 { + out.ExactModelMappings = make(map[string]string, len(cfg.ExactModelMappings)) + for requestedModel, mappedModel := range cfg.ExactModelMappings { + requestedModel = strings.TrimSpace(requestedModel) + mappedModel = normalizeOpenAIMessagesDispatchMappedModel(mappedModel) + if requestedModel == "" || mappedModel == "" { + continue + } + out.ExactModelMappings[requestedModel] = mappedModel + } + if len(out.ExactModelMappings) == 0 { + out.ExactModelMappings = nil + } + } + + return out +} + +func claudeMessagesDispatchFamily(model string) string { + normalized := strings.ToLower(strings.TrimSpace(model)) + if !strings.HasPrefix(normalized, "claude") { + return "" + } + switch { + case strings.Contains(normalized, "opus"): + return "opus" + case strings.Contains(normalized, "sonnet"): + return "sonnet" + case strings.Contains(normalized, "haiku"): + return "haiku" + default: + return "" + } +} + +func (g *Group) ResolveMessagesDispatchModel(requestedModel string) string { + if g == nil { + return "" + } + requestedModel = strings.TrimSpace(requestedModel) + if requestedModel == "" { + return "" + } + + cfg := normalizeOpenAIMessagesDispatchModelConfig(g.MessagesDispatchModelConfig) + if mappedModel := strings.TrimSpace(cfg.ExactModelMappings[requestedModel]); mappedModel != "" { + return mappedModel + } + + switch claudeMessagesDispatchFamily(requestedModel) { + case "opus": + if mappedModel := strings.TrimSpace(cfg.OpusMappedModel); mappedModel != "" { + return mappedModel + } + return defaultOpenAIMessagesDispatchOpusMappedModel + case "sonnet": + if mappedModel := strings.TrimSpace(cfg.SonnetMappedModel); mappedModel != "" { + return mappedModel + } + return defaultOpenAIMessagesDispatchSonnetMappedModel + case "haiku": + if mappedModel := strings.TrimSpace(cfg.HaikuMappedModel); mappedModel != "" { + return mappedModel + } + return defaultOpenAIMessagesDispatchHaikuMappedModel + default: + return "" + } +} + +func sanitizeGroupMessagesDispatchFields(g *Group) { + if g == nil || g.Platform == PlatformOpenAI { + return + } + g.AllowMessagesDispatch = false + g.DefaultMappedModel = "" + g.MessagesDispatchModelConfig = OpenAIMessagesDispatchModelConfig{} +} diff --git a/backend/internal/service/openai_messages_dispatch_test.go b/backend/internal/service/openai_messages_dispatch_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a625aaddd4359e7ca04bb817420061a63db7040f --- /dev/null +++ b/backend/internal/service/openai_messages_dispatch_test.go @@ -0,0 +1,27 @@ +package service + +import "testing" + +import "github.com/stretchr/testify/require" + +func TestNormalizeOpenAIMessagesDispatchModelConfig(t *testing.T) { + t.Parallel() + + cfg := normalizeOpenAIMessagesDispatchModelConfig(OpenAIMessagesDispatchModelConfig{ + OpusMappedModel: " gpt-5.4-high ", + SonnetMappedModel: "gpt-5.3-codex", + HaikuMappedModel: " gpt-5.4-mini-medium ", + ExactModelMappings: map[string]string{ + " claude-sonnet-4-5-20250929 ": " gpt-5.2-high ", + "": "gpt-5.4", + "claude-opus-4-6": " ", + }, + }) + + require.Equal(t, "gpt-5.4", cfg.OpusMappedModel) + require.Equal(t, "gpt-5.3-codex", cfg.SonnetMappedModel) + require.Equal(t, "gpt-5.4-mini", cfg.HaikuMappedModel) + require.Equal(t, map[string]string{ + "claude-sonnet-4-5-20250929": "gpt-5.2", + }, cfg.ExactModelMappings) +} diff --git a/backend/internal/service/openai_ws_ratelimit_signal_test.go b/backend/internal/service/openai_ws_ratelimit_signal_test.go index ffe7915262483670a6fc6907a5d4d48c62081271..6313d0c08e1c742e17fde86d5a7fb8ce97e0b58e 100644 --- a/backend/internal/service/openai_ws_ratelimit_signal_test.go +++ b/backend/internal/service/openai_ws_ratelimit_signal_test.go @@ -492,7 +492,7 @@ func TestAdminService_ListAccounts_ExhaustedCodexExtraReturnsRateLimitedAccount( } svc := &adminServiceImpl{accountRepo: repo} - accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, "", "", 0, "") + accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, "", "", 0, "", "", "") require.NoError(t, err) require.Equal(t, int64(1), total) require.Len(t, accounts, 1) diff --git a/backend/internal/service/ops_service.go b/backend/internal/service/ops_service.go index 29f0aa8b5089bee575a0b0ab7c9b4fa13b3d1327..cd3974a00f06bfc65ff57e7f4a168fddec18983b 100644 --- a/backend/internal/service/ops_service.go +++ b/backend/internal/service/ops_service.go @@ -16,7 +16,7 @@ import ( var ErrOpsDisabled = infraerrors.NotFound("OPS_DISABLED", "Ops monitoring is disabled") const ( - opsMaxStoredRequestBodyBytes = 10 * 1024 + opsMaxStoredRequestBodyBytes = 256 * 1024 opsMaxStoredErrorBodyBytes = 20 * 1024 ) diff --git a/backend/internal/service/payment_config_limits.go b/backend/internal/service/payment_config_limits.go new file mode 100644 index 0000000000000000000000000000000000000000..569052788564c04c21fa164299e99c20832edb7b --- /dev/null +++ b/backend/internal/service/payment_config_limits.go @@ -0,0 +1,172 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +// GetAvailableMethodLimits collects all payment types from enabled provider +// instances and returns limits for each, plus the global widest range. +// Stripe sub-types (card, link) are aggregated under "stripe". +func (s *PaymentConfigService) GetAvailableMethodLimits(ctx context.Context) (*MethodLimitsResponse, error) { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Where(paymentproviderinstance.EnabledEQ(true)).All(ctx) + if err != nil { + return nil, fmt.Errorf("query provider instances: %w", err) + } + typeInstances := pcGroupByPaymentType(instances) + resp := &MethodLimitsResponse{ + Methods: make(map[string]MethodLimits, len(typeInstances)), + } + for pt, insts := range typeInstances { + ml := pcAggregateMethodLimits(pt, insts) + resp.Methods[ml.PaymentType] = ml + } + resp.GlobalMin, resp.GlobalMax = pcComputeGlobalRange(resp.Methods) + return resp, nil +} + +// GetMethodLimits returns per-payment-type limits from enabled provider instances. +func (s *PaymentConfigService) GetMethodLimits(ctx context.Context, types []string) ([]MethodLimits, error) { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Where(paymentproviderinstance.EnabledEQ(true)).All(ctx) + if err != nil { + return nil, fmt.Errorf("query provider instances: %w", err) + } + result := make([]MethodLimits, 0, len(types)) + for _, pt := range types { + var matching []*dbent.PaymentProviderInstance + for _, inst := range instances { + if payment.InstanceSupportsType(inst.SupportedTypes, pt) { + matching = append(matching, inst) + } + } + result = append(result, pcAggregateMethodLimits(pt, matching)) + } + return result, nil +} + +// pcGroupByPaymentType groups instances by user-facing payment type. +// For Stripe providers, ALL sub-types (card, link, alipay, wxpay) map to "stripe" +// because the user sees a single "Stripe" button, not individual sub-methods. +// Uses a seen set to avoid counting one instance twice. +func pcGroupByPaymentType(instances []*dbent.PaymentProviderInstance) map[string][]*dbent.PaymentProviderInstance { + typeInstances := make(map[string][]*dbent.PaymentProviderInstance) + seen := make(map[string]map[int64]bool) + add := func(key string, inst *dbent.PaymentProviderInstance) { + if seen[key] == nil { + seen[key] = make(map[int64]bool) + } + if !seen[key][int64(inst.ID)] { + seen[key][int64(inst.ID)] = true + typeInstances[key] = append(typeInstances[key], inst) + } + } + for _, inst := range instances { + // Stripe provider: all sub-types → single "stripe" group + if inst.ProviderKey == payment.TypeStripe { + add(payment.TypeStripe, inst) + continue + } + for _, t := range splitTypes(inst.SupportedTypes) { + add(t, inst) + } + } + return typeInstances +} + +// pcInstanceTypeLimits extracts per-type limits from a provider instance. +// Returns (limits, true) if configured; (zero, false) if unlimited. +// For Stripe instances, limits are stored under "stripe" key regardless of sub-types. +func pcInstanceTypeLimits(inst *dbent.PaymentProviderInstance, pt string) (payment.ChannelLimits, bool) { + if inst.Limits == "" { + return payment.ChannelLimits{}, false + } + var limits payment.InstanceLimits + if err := json.Unmarshal([]byte(inst.Limits), &limits); err != nil { + return payment.ChannelLimits{}, false + } + cl, ok := limits[pt] + return cl, ok +} + +// unionFloat merges a single limit value into the aggregate using UNION semantics. +// - For "min" fields (wantMin=true): keeps the lowest non-zero value +// - For "max"/"cap" fields (wantMin=false): keeps the highest non-zero value +// - If any value is 0 (unlimited), the result is unlimited. +// +// Returns (aggregated value, still limited). +func unionFloat(agg float64, limited bool, val float64, wantMin bool) (float64, bool) { + if val == 0 { + return agg, false + } + if !limited { + return agg, false + } + if agg == 0 { + return val, true + } + if wantMin && val < agg { + return val, true + } + if !wantMin && val > agg { + return val, true + } + return agg, true +} + +// pcAggregateMethodLimits computes the UNION (least restrictive) of limits +// across all provider instances for a given payment type. +// +// Since the load balancer can route an order to any available instance, +// the user should see the widest possible range: +// - SingleMin: lowest floor across instances; 0 if any is unlimited +// - SingleMax: highest ceiling across instances; 0 if any is unlimited +// - DailyLimit: highest cap across instances; 0 if any is unlimited +func pcAggregateMethodLimits(pt string, instances []*dbent.PaymentProviderInstance) MethodLimits { + ml := MethodLimits{PaymentType: pt} + minLimited, maxLimited, dailyLimited := true, true, true + + for _, inst := range instances { + cl, hasLimits := pcInstanceTypeLimits(inst, pt) + if !hasLimits { + return MethodLimits{PaymentType: pt} // any unlimited instance → all zeros + } + ml.SingleMin, minLimited = unionFloat(ml.SingleMin, minLimited, cl.SingleMin, true) + ml.SingleMax, maxLimited = unionFloat(ml.SingleMax, maxLimited, cl.SingleMax, false) + ml.DailyLimit, dailyLimited = unionFloat(ml.DailyLimit, dailyLimited, cl.DailyLimit, false) + } + + if !minLimited { + ml.SingleMin = 0 + } + if !maxLimited { + ml.SingleMax = 0 + } + if !dailyLimited { + ml.DailyLimit = 0 + } + return ml +} + +// pcComputeGlobalRange computes the widest [min, max] across all methods. +// Uses the same union logic: lowest min, highest max, 0 if any is unlimited. +func pcComputeGlobalRange(methods map[string]MethodLimits) (globalMin, globalMax float64) { + minLimited, maxLimited := true, true + for _, ml := range methods { + globalMin, minLimited = unionFloat(globalMin, minLimited, ml.SingleMin, true) + globalMax, maxLimited = unionFloat(globalMax, maxLimited, ml.SingleMax, false) + } + if !minLimited { + globalMin = 0 + } + if !maxLimited { + globalMax = 0 + } + return globalMin, globalMax +} diff --git a/backend/internal/service/payment_config_limits_test.go b/backend/internal/service/payment_config_limits_test.go new file mode 100644 index 0000000000000000000000000000000000000000..73ad66ef03c60127040d391e7dfb091ebced8dc9 --- /dev/null +++ b/backend/internal/service/payment_config_limits_test.go @@ -0,0 +1,301 @@ +package service + +import ( + "testing" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +func TestUnionFloat(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + agg float64 + limited bool + val float64 + wantMin bool + wantAgg float64 + wantLimited bool + }{ + {"first non-zero value", 0, true, 5, true, 5, true}, + {"lower min replaces", 10, true, 3, true, 3, true}, + {"higher min does not replace", 3, true, 10, true, 3, true}, + {"higher max replaces", 10, true, 20, false, 20, true}, + {"lower max does not replace", 20, true, 10, false, 20, true}, + {"zero value makes unlimited", 5, true, 0, true, 5, false}, + {"already unlimited stays unlimited", 5, false, 10, true, 5, false}, + {"zero on first call", 0, true, 0, true, 0, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + gotAgg, gotLimited := unionFloat(tt.agg, tt.limited, tt.val, tt.wantMin) + if gotAgg != tt.wantAgg || gotLimited != tt.wantLimited { + t.Fatalf("unionFloat(%v, %v, %v, %v) = (%v, %v), want (%v, %v)", + tt.agg, tt.limited, tt.val, tt.wantMin, + gotAgg, gotLimited, tt.wantAgg, tt.wantLimited) + } + }) + } +} + +func makeInstance(id int64, providerKey, supportedTypes, limits string) *dbent.PaymentProviderInstance { + return &dbent.PaymentProviderInstance{ + ID: id, + ProviderKey: providerKey, + SupportedTypes: supportedTypes, + Limits: limits, + Enabled: true, + } +} + +func TestPcAggregateMethodLimits(t *testing.T) { + t.Parallel() + + t.Run("single instance with limits", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay,wxpay", + `{"alipay":{"singleMin":2,"singleMax":14},"wxpay":{"singleMin":1,"singleMax":12}}`) + ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst}) + if ml.SingleMin != 2 || ml.SingleMax != 14 { + t.Fatalf("alipay limits = min:%v max:%v, want min:2 max:14", ml.SingleMin, ml.SingleMax) + } + }) + + t.Run("two instances union takes widest range", func(t *testing.T) { + t.Parallel() + inst1 := makeInstance(1, "easypay", "alipay,wxpay", + `{"alipay":{"singleMin":5,"singleMax":100}}`) + inst2 := makeInstance(2, "easypay", "alipay,wxpay", + `{"alipay":{"singleMin":2,"singleMax":200}}`) + ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst1, inst2}) + if ml.SingleMin != 2 { + t.Fatalf("SingleMin = %v, want 2 (lowest floor)", ml.SingleMin) + } + if ml.SingleMax != 200 { + t.Fatalf("SingleMax = %v, want 200 (highest ceiling)", ml.SingleMax) + } + }) + + t.Run("one instance unlimited makes aggregate unlimited", func(t *testing.T) { + t.Parallel() + inst1 := makeInstance(1, "easypay", "wxpay", + `{"wxpay":{"singleMin":3,"singleMax":10}}`) + inst2 := makeInstance(2, "easypay", "wxpay", "") // no limits = unlimited + ml := pcAggregateMethodLimits("wxpay", []*dbent.PaymentProviderInstance{inst1, inst2}) + if ml.SingleMin != 0 || ml.SingleMax != 0 { + t.Fatalf("limits = min:%v max:%v, want min:0 max:0 (unlimited)", ml.SingleMin, ml.SingleMax) + } + }) + + t.Run("one field unlimited others limited", func(t *testing.T) { + t.Parallel() + inst1 := makeInstance(1, "easypay", "alipay", + `{"alipay":{"singleMin":5,"singleMax":100}}`) + inst2 := makeInstance(2, "easypay", "alipay", + `{"alipay":{"singleMin":3,"singleMax":0}}`) // singleMax=0 = unlimited + ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst1, inst2}) + if ml.SingleMin != 3 { + t.Fatalf("SingleMin = %v, want 3 (lowest floor)", ml.SingleMin) + } + if ml.SingleMax != 0 { + t.Fatalf("SingleMax = %v, want 0 (unlimited)", ml.SingleMax) + } + }) + + t.Run("empty instances returns zeros", func(t *testing.T) { + t.Parallel() + ml := pcAggregateMethodLimits("alipay", nil) + if ml.SingleMin != 0 || ml.SingleMax != 0 || ml.DailyLimit != 0 { + t.Fatalf("empty instances should return all zeros, got %+v", ml) + } + }) + + t.Run("invalid JSON treated as unlimited", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay", `{invalid json}`) + ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst}) + if ml.SingleMin != 0 || ml.SingleMax != 0 { + t.Fatalf("invalid JSON should be treated as unlimited, got %+v", ml) + } + }) + + t.Run("type not in limits JSON treated as unlimited", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay,wxpay", + `{"wxpay":{"singleMin":1,"singleMax":10}}`) // only wxpay, no alipay + ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst}) + if ml.SingleMin != 0 || ml.SingleMax != 0 { + t.Fatalf("missing type should be treated as unlimited, got %+v", ml) + } + }) + + t.Run("daily limit aggregation", func(t *testing.T) { + t.Parallel() + inst1 := makeInstance(1, "easypay", "alipay", + `{"alipay":{"singleMin":1,"singleMax":100,"dailyLimit":500}}`) + inst2 := makeInstance(2, "easypay", "alipay", + `{"alipay":{"singleMin":2,"singleMax":200,"dailyLimit":1000}}`) + ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst1, inst2}) + if ml.DailyLimit != 1000 { + t.Fatalf("DailyLimit = %v, want 1000 (highest cap)", ml.DailyLimit) + } + }) +} + +func TestPcGroupByPaymentType(t *testing.T) { + t.Parallel() + + t.Run("stripe instance maps all types to stripe group", func(t *testing.T) { + t.Parallel() + stripe := makeInstance(1, payment.TypeStripe, "card,alipay,link,wxpay", "") + easypay := makeInstance(2, payment.TypeEasyPay, "alipay,wxpay", "") + + groups := pcGroupByPaymentType([]*dbent.PaymentProviderInstance{stripe, easypay}) + + // Stripe instance should only be in "stripe" group + if len(groups[payment.TypeStripe]) != 1 || groups[payment.TypeStripe][0].ID != 1 { + t.Fatalf("stripe group should contain only stripe instance, got %v", groups[payment.TypeStripe]) + } + // alipay group should only contain easypay, NOT stripe + if len(groups[payment.TypeAlipay]) != 1 || groups[payment.TypeAlipay][0].ID != 2 { + t.Fatalf("alipay group should contain only easypay instance, got %v", groups[payment.TypeAlipay]) + } + // wxpay group should only contain easypay, NOT stripe + if len(groups[payment.TypeWxpay]) != 1 || groups[payment.TypeWxpay][0].ID != 2 { + t.Fatalf("wxpay group should contain only easypay instance, got %v", groups[payment.TypeWxpay]) + } + }) + + t.Run("multiple easypay instances in same groups", func(t *testing.T) { + t.Parallel() + ep1 := makeInstance(1, payment.TypeEasyPay, "alipay,wxpay", "") + ep2 := makeInstance(2, payment.TypeEasyPay, "alipay,wxpay", "") + + groups := pcGroupByPaymentType([]*dbent.PaymentProviderInstance{ep1, ep2}) + + if len(groups[payment.TypeAlipay]) != 2 { + t.Fatalf("alipay group should have 2 instances, got %d", len(groups[payment.TypeAlipay])) + } + if len(groups[payment.TypeWxpay]) != 2 { + t.Fatalf("wxpay group should have 2 instances, got %d", len(groups[payment.TypeWxpay])) + } + }) + + t.Run("stripe with no supported types still in stripe group", func(t *testing.T) { + t.Parallel() + stripe := makeInstance(1, payment.TypeStripe, "", "") + + groups := pcGroupByPaymentType([]*dbent.PaymentProviderInstance{stripe}) + + if len(groups[payment.TypeStripe]) != 1 { + t.Fatalf("stripe with empty types should still be in stripe group, got %v", groups) + } + }) +} + +func TestPcComputeGlobalRange(t *testing.T) { + t.Parallel() + + t.Run("all methods have limits", func(t *testing.T) { + t.Parallel() + methods := map[string]MethodLimits{ + "alipay": {SingleMin: 2, SingleMax: 14}, + "wxpay": {SingleMin: 1, SingleMax: 12}, + "stripe": {SingleMin: 5, SingleMax: 100}, + } + gMin, gMax := pcComputeGlobalRange(methods) + if gMin != 1 { + t.Fatalf("global min = %v, want 1 (lowest floor)", gMin) + } + if gMax != 100 { + t.Fatalf("global max = %v, want 100 (highest ceiling)", gMax) + } + }) + + t.Run("one method unlimited makes global unlimited", func(t *testing.T) { + t.Parallel() + methods := map[string]MethodLimits{ + "alipay": {SingleMin: 2, SingleMax: 14}, + "stripe": {SingleMin: 0, SingleMax: 0}, // unlimited + } + gMin, gMax := pcComputeGlobalRange(methods) + if gMin != 0 { + t.Fatalf("global min = %v, want 0 (unlimited)", gMin) + } + if gMax != 0 { + t.Fatalf("global max = %v, want 0 (unlimited)", gMax) + } + }) + + t.Run("empty methods returns zeros", func(t *testing.T) { + t.Parallel() + gMin, gMax := pcComputeGlobalRange(map[string]MethodLimits{}) + if gMin != 0 || gMax != 0 { + t.Fatalf("empty methods should return (0, 0), got (%v, %v)", gMin, gMax) + } + }) + + t.Run("only min unlimited", func(t *testing.T) { + t.Parallel() + methods := map[string]MethodLimits{ + "alipay": {SingleMin: 0, SingleMax: 100}, + "wxpay": {SingleMin: 5, SingleMax: 50}, + } + gMin, gMax := pcComputeGlobalRange(methods) + if gMin != 0 { + t.Fatalf("global min = %v, want 0 (unlimited)", gMin) + } + if gMax != 100 { + t.Fatalf("global max = %v, want 100", gMax) + } + }) +} + +func TestPcInstanceTypeLimits(t *testing.T) { + t.Parallel() + + t.Run("empty limits string returns false", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay", "") + _, ok := pcInstanceTypeLimits(inst, "alipay") + if ok { + t.Fatal("expected ok=false for empty limits") + } + }) + + t.Run("type found returns correct values", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay", + `{"alipay":{"singleMin":2,"singleMax":14,"dailyLimit":500}}`) + cl, ok := pcInstanceTypeLimits(inst, "alipay") + if !ok { + t.Fatal("expected ok=true") + } + if cl.SingleMin != 2 || cl.SingleMax != 14 || cl.DailyLimit != 500 { + t.Fatalf("limits = %+v, want min:2 max:14 daily:500", cl) + } + }) + + t.Run("type not found returns false", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay", + `{"wxpay":{"singleMin":1}}`) + _, ok := pcInstanceTypeLimits(inst, "alipay") + if ok { + t.Fatal("expected ok=false for missing type") + } + }) + + t.Run("invalid JSON returns false", func(t *testing.T) { + t.Parallel() + inst := makeInstance(1, "easypay", "alipay", `{bad json}`) + _, ok := pcInstanceTypeLimits(inst, "alipay") + if ok { + t.Fatal("expected ok=false for invalid JSON") + } + }) +} diff --git a/backend/internal/service/payment_config_plans.go b/backend/internal/service/payment_config_plans.go new file mode 100644 index 0000000000000000000000000000000000000000..8a3e2950cce26e344f05f87e046eb259de129b06 --- /dev/null +++ b/backend/internal/service/payment_config_plans.go @@ -0,0 +1,147 @@ +package service + +import ( + "context" + "fmt" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/group" + "github.com/Wei-Shaw/sub2api/ent/subscriptionplan" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// --- Plan CRUD --- + +// PlanGroupInfo holds the group details needed for subscription plan display. +type PlanGroupInfo struct { + Platform string `json:"platform"` + Name string `json:"name"` + RateMultiplier float64 `json:"rate_multiplier"` + DailyLimitUSD *float64 `json:"daily_limit_usd"` + WeeklyLimitUSD *float64 `json:"weekly_limit_usd"` + MonthlyLimitUSD *float64 `json:"monthly_limit_usd"` + ModelScopes []string `json:"supported_model_scopes"` +} + +// GetGroupPlatformMap returns a map of group_id → platform for the given plans. +func (s *PaymentConfigService) GetGroupPlatformMap(ctx context.Context, plans []*dbent.SubscriptionPlan) map[int64]string { + info := s.GetGroupInfoMap(ctx, plans) + m := make(map[int64]string, len(info)) + for id, gi := range info { + m[id] = gi.Platform + } + return m +} + +// GetGroupInfoMap returns a map of group_id → PlanGroupInfo for the given plans. +func (s *PaymentConfigService) GetGroupInfoMap(ctx context.Context, plans []*dbent.SubscriptionPlan) map[int64]PlanGroupInfo { + ids := make([]int64, 0, len(plans)) + seen := make(map[int64]bool) + for _, p := range plans { + if !seen[p.GroupID] { + seen[p.GroupID] = true + ids = append(ids, p.GroupID) + } + } + if len(ids) == 0 { + return nil + } + groups, err := s.entClient.Group.Query().Where(group.IDIn(ids...)).All(ctx) + if err != nil { + return nil + } + m := make(map[int64]PlanGroupInfo, len(groups)) + for _, g := range groups { + m[int64(g.ID)] = PlanGroupInfo{ + Platform: g.Platform, + Name: g.Name, + RateMultiplier: g.RateMultiplier, + DailyLimitUSD: g.DailyLimitUsd, + WeeklyLimitUSD: g.WeeklyLimitUsd, + MonthlyLimitUSD: g.MonthlyLimitUsd, + ModelScopes: g.SupportedModelScopes, + } + } + return m +} + +func (s *PaymentConfigService) ListPlans(ctx context.Context) ([]*dbent.SubscriptionPlan, error) { + return s.entClient.SubscriptionPlan.Query().Order(subscriptionplan.BySortOrder()).All(ctx) +} + +func (s *PaymentConfigService) ListPlansForSale(ctx context.Context) ([]*dbent.SubscriptionPlan, error) { + return s.entClient.SubscriptionPlan.Query().Where(subscriptionplan.ForSaleEQ(true)).Order(subscriptionplan.BySortOrder()).All(ctx) +} + +func (s *PaymentConfigService) CreatePlan(ctx context.Context, req CreatePlanRequest) (*dbent.SubscriptionPlan, error) { + b := s.entClient.SubscriptionPlan.Create(). + SetGroupID(req.GroupID).SetName(req.Name).SetDescription(req.Description). + SetPrice(req.Price).SetValidityDays(req.ValidityDays).SetValidityUnit(req.ValidityUnit). + SetFeatures(req.Features).SetProductName(req.ProductName). + SetForSale(req.ForSale).SetSortOrder(req.SortOrder) + if req.OriginalPrice != nil { + b.SetOriginalPrice(*req.OriginalPrice) + } + return b.Save(ctx) +} + +// UpdatePlan updates a subscription plan by ID (patch semantics). +// NOTE: This function exceeds 30 lines due to per-field nil-check patch update boilerplate. +func (s *PaymentConfigService) UpdatePlan(ctx context.Context, id int64, req UpdatePlanRequest) (*dbent.SubscriptionPlan, error) { + u := s.entClient.SubscriptionPlan.UpdateOneID(id) + if req.GroupID != nil { + u.SetGroupID(*req.GroupID) + } + if req.Name != nil { + u.SetName(*req.Name) + } + if req.Description != nil { + u.SetDescription(*req.Description) + } + if req.Price != nil { + u.SetPrice(*req.Price) + } + if req.OriginalPrice != nil { + u.SetOriginalPrice(*req.OriginalPrice) + } + if req.ValidityDays != nil { + u.SetValidityDays(*req.ValidityDays) + } + if req.ValidityUnit != nil { + u.SetValidityUnit(*req.ValidityUnit) + } + if req.Features != nil { + u.SetFeatures(*req.Features) + } + if req.ProductName != nil { + u.SetProductName(*req.ProductName) + } + if req.ForSale != nil { + u.SetForSale(*req.ForSale) + } + if req.SortOrder != nil { + u.SetSortOrder(*req.SortOrder) + } + return u.Save(ctx) +} + +func (s *PaymentConfigService) DeletePlan(ctx context.Context, id int64) error { + count, err := s.countPendingOrdersByPlan(ctx, id) + if err != nil { + return fmt.Errorf("check pending orders: %w", err) + } + if count > 0 { + return infraerrors.Conflict("PENDING_ORDERS", + fmt.Sprintf("this plan has %d in-progress orders and cannot be deleted — wait for orders to complete first", count)) + } + return s.entClient.SubscriptionPlan.DeleteOneID(id).Exec(ctx) +} + +// GetPlan returns a subscription plan by ID. +func (s *PaymentConfigService) GetPlan(ctx context.Context, id int64) (*dbent.SubscriptionPlan, error) { + plan, err := s.entClient.SubscriptionPlan.Get(ctx, id) + if err != nil { + return nil, infraerrors.NotFound("PLAN_NOT_FOUND", "subscription plan not found") + } + return plan, nil +} diff --git a/backend/internal/service/payment_config_providers.go b/backend/internal/service/payment_config_providers.go new file mode 100644 index 0000000000000000000000000000000000000000..101819144233e3d612b5aaea0a4bdb07deae4136 --- /dev/null +++ b/backend/internal/service/payment_config_providers.go @@ -0,0 +1,286 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/internal/payment" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// --- Provider Instance CRUD --- + +func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*dbent.PaymentProviderInstance, error) { + return s.entClient.PaymentProviderInstance.Query().Order(paymentproviderinstance.BySortOrder()).All(ctx) +} + +// ProviderInstanceResponse is the API response for a provider instance. +type ProviderInstanceResponse struct { + ID int64 `json:"id"` + ProviderKey string `json:"provider_key"` + Name string `json:"name"` + Config map[string]string `json:"config"` + SupportedTypes []string `json:"supported_types"` + Limits string `json:"limits"` + Enabled bool `json:"enabled"` + RefundEnabled bool `json:"refund_enabled"` + SortOrder int `json:"sort_order"` + PaymentMode string `json:"payment_mode"` +} + +// ListProviderInstancesWithConfig returns provider instances with decrypted config. +func (s *PaymentConfigService) ListProviderInstancesWithConfig(ctx context.Context) ([]ProviderInstanceResponse, error) { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Order(paymentproviderinstance.BySortOrder()).All(ctx) + if err != nil { + return nil, err + } + result := make([]ProviderInstanceResponse, 0, len(instances)) + for _, inst := range instances { + resp := ProviderInstanceResponse{ + ID: int64(inst.ID), ProviderKey: inst.ProviderKey, Name: inst.Name, + SupportedTypes: splitTypes(inst.SupportedTypes), Limits: inst.Limits, + Enabled: inst.Enabled, RefundEnabled: inst.RefundEnabled, SortOrder: inst.SortOrder, + PaymentMode: inst.PaymentMode, + } + resp.Config, err = s.decryptAndMaskConfig(inst.Config) + if err != nil { + return nil, fmt.Errorf("decrypt config for instance %d: %w", inst.ID, err) + } + result = append(result, resp) + } + return result, nil +} + +func (s *PaymentConfigService) decryptAndMaskConfig(encrypted string) (map[string]string, error) { + return s.decryptConfig(encrypted) +} + +// pendingOrderStatuses are order statuses considered "in progress". +var pendingOrderStatuses = []string{ + payment.OrderStatusPending, + payment.OrderStatusPaid, + payment.OrderStatusRecharging, +} + +var sensitiveConfigPatterns = []string{"key", "pkey", "secret", "private", "password"} + +func isSensitiveConfigField(fieldName string) bool { + lower := strings.ToLower(fieldName) + for _, p := range sensitiveConfigPatterns { + if strings.Contains(lower, p) { + return true + } + } + return false +} + +func (s *PaymentConfigService) countPendingOrders(ctx context.Context, providerInstanceID int64) (int, error) { + return s.entClient.PaymentOrder.Query(). + Where( + paymentorder.ProviderInstanceIDEQ(strconv.FormatInt(providerInstanceID, 10)), + paymentorder.StatusIn(pendingOrderStatuses...), + ).Count(ctx) +} + +func (s *PaymentConfigService) countPendingOrdersByPlan(ctx context.Context, planID int64) (int, error) { + return s.entClient.PaymentOrder.Query(). + Where( + paymentorder.PlanIDEQ(planID), + paymentorder.StatusIn(pendingOrderStatuses...), + ).Count(ctx) +} + +var validProviderKeys = map[string]bool{ + payment.TypeEasyPay: true, payment.TypeAlipay: true, payment.TypeWxpay: true, payment.TypeStripe: true, +} + +func (s *PaymentConfigService) CreateProviderInstance(ctx context.Context, req CreateProviderInstanceRequest) (*dbent.PaymentProviderInstance, error) { + typesStr := joinTypes(req.SupportedTypes) + if err := validateProviderRequest(req.ProviderKey, req.Name, typesStr); err != nil { + return nil, err + } + enc, err := s.encryptConfig(req.Config) + if err != nil { + return nil, err + } + return s.entClient.PaymentProviderInstance.Create(). + SetProviderKey(req.ProviderKey).SetName(req.Name).SetConfig(enc). + SetSupportedTypes(typesStr).SetEnabled(req.Enabled).SetPaymentMode(req.PaymentMode). + SetSortOrder(req.SortOrder).SetLimits(req.Limits).SetRefundEnabled(req.RefundEnabled). + Save(ctx) +} + +func validateProviderRequest(providerKey, name, supportedTypes string) error { + if strings.TrimSpace(name) == "" { + return infraerrors.BadRequest("VALIDATION_ERROR", "provider name is required") + } + if !validProviderKeys[providerKey] { + return infraerrors.BadRequest("VALIDATION_ERROR", fmt.Sprintf("invalid provider key: %s", providerKey)) + } + // supported_types can be empty (provider accepts no payment types until configured) + return nil +} + +// UpdateProviderInstance updates a provider instance by ID (patch semantics). +// NOTE: This function exceeds 30 lines due to per-field nil-check patch update +// boilerplate and pending-order safety checks. +func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id int64, req UpdateProviderInstanceRequest) (*dbent.PaymentProviderInstance, error) { + if req.Config != nil { + hasSensitive := false + for k := range req.Config { + if isSensitiveConfigField(k) && req.Config[k] != "" { + hasSensitive = true + break + } + } + if hasSensitive { + count, err := s.countPendingOrders(ctx, id) + if err != nil { + return nil, fmt.Errorf("check pending orders: %w", err) + } + if count > 0 { + return nil, infraerrors.Conflict("PENDING_ORDERS", "instance has pending orders"). + WithMetadata(map[string]string{"count": strconv.Itoa(count)}) + } + } + } + if req.Enabled != nil && !*req.Enabled { + count, err := s.countPendingOrders(ctx, id) + if err != nil { + return nil, fmt.Errorf("check pending orders: %w", err) + } + if count > 0 { + return nil, infraerrors.Conflict("PENDING_ORDERS", "instance has pending orders"). + WithMetadata(map[string]string{"count": strconv.Itoa(count)}) + } + } + u := s.entClient.PaymentProviderInstance.UpdateOneID(id) + if req.Name != nil { + u.SetName(*req.Name) + } + if req.Config != nil { + merged, err := s.mergeConfig(ctx, id, req.Config) + if err != nil { + return nil, err + } + enc, err := s.encryptConfig(merged) + if err != nil { + return nil, err + } + u.SetConfig(enc) + } + if req.SupportedTypes != nil { + // Check pending orders before removing payment types + count, err := s.countPendingOrders(ctx, id) + if err != nil { + return nil, fmt.Errorf("check pending orders: %w", err) + } + if count > 0 { + // Load current instance to compare types + inst, err := s.entClient.PaymentProviderInstance.Get(ctx, id) + if err != nil { + return nil, fmt.Errorf("load provider instance: %w", err) + } + oldTypes := strings.Split(inst.SupportedTypes, ",") + newTypes := req.SupportedTypes + for _, ot := range oldTypes { + ot = strings.TrimSpace(ot) + if ot == "" { + continue + } + found := false + for _, nt := range newTypes { + if strings.TrimSpace(nt) == ot { + found = true + break + } + } + if !found { + return nil, infraerrors.Conflict("PENDING_ORDERS", "cannot remove payment types while instance has pending orders"). + WithMetadata(map[string]string{"count": strconv.Itoa(count)}) + } + } + } + u.SetSupportedTypes(joinTypes(req.SupportedTypes)) + } + if req.Enabled != nil { + u.SetEnabled(*req.Enabled) + } + if req.SortOrder != nil { + u.SetSortOrder(*req.SortOrder) + } + if req.Limits != nil { + u.SetLimits(*req.Limits) + } + if req.RefundEnabled != nil { + u.SetRefundEnabled(*req.RefundEnabled) + } + if req.PaymentMode != nil { + u.SetPaymentMode(*req.PaymentMode) + } + return u.Save(ctx) +} + +func (s *PaymentConfigService) mergeConfig(ctx context.Context, id int64, newConfig map[string]string) (map[string]string, error) { + inst, err := s.entClient.PaymentProviderInstance.Get(ctx, id) + if err != nil { + return nil, fmt.Errorf("load existing provider: %w", err) + } + existing, err := s.decryptConfig(inst.Config) + if err != nil { + return nil, fmt.Errorf("decrypt existing config for instance %d: %w", id, err) + } + if existing == nil { + return newConfig, nil + } + for k, v := range newConfig { + existing[k] = v + } + return existing, nil +} + +func (s *PaymentConfigService) decryptConfig(encrypted string) (map[string]string, error) { + if encrypted == "" { + return nil, nil + } + decrypted, err := payment.Decrypt(encrypted, s.encryptionKey) + if err != nil { + return nil, fmt.Errorf("decrypt config: %w", err) + } + var raw map[string]string + if err := json.Unmarshal([]byte(decrypted), &raw); err != nil { + return nil, fmt.Errorf("unmarshal decrypted config: %w", err) + } + return raw, nil +} + +func (s *PaymentConfigService) DeleteProviderInstance(ctx context.Context, id int64) error { + count, err := s.countPendingOrders(ctx, id) + if err != nil { + return fmt.Errorf("check pending orders: %w", err) + } + if count > 0 { + return infraerrors.Conflict("PENDING_ORDERS", + fmt.Sprintf("this instance has %d in-progress orders and cannot be deleted — wait for orders to complete or disable the instance first", count)) + } + return s.entClient.PaymentProviderInstance.DeleteOneID(id).Exec(ctx) +} + +func (s *PaymentConfigService) encryptConfig(cfg map[string]string) (string, error) { + data, err := json.Marshal(cfg) + if err != nil { + return "", fmt.Errorf("marshal config: %w", err) + } + enc, err := payment.Encrypt(string(data), s.encryptionKey) + if err != nil { + return "", fmt.Errorf("encrypt config: %w", err) + } + return enc, nil +} diff --git a/backend/internal/service/payment_config_providers_test.go b/backend/internal/service/payment_config_providers_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e71eb9f502b78db9d52a91ad5ccadba47707ddb6 --- /dev/null +++ b/backend/internal/service/payment_config_providers_test.go @@ -0,0 +1,187 @@ +//go:build unit + +package service + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidateProviderRequest(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + providerKey string + providerName string + supportedTypes string + wantErr bool + errContains string + }{ + { + name: "valid easypay with types", + providerKey: "easypay", + providerName: "MyProvider", + supportedTypes: "alipay,wxpay", + wantErr: false, + }, + { + name: "valid stripe with empty types", + providerKey: "stripe", + providerName: "Stripe Provider", + supportedTypes: "", + wantErr: false, + }, + { + name: "valid alipay provider", + providerKey: "alipay", + providerName: "Alipay Direct", + supportedTypes: "alipay", + wantErr: false, + }, + { + name: "valid wxpay provider", + providerKey: "wxpay", + providerName: "WeChat Pay", + supportedTypes: "wxpay", + wantErr: false, + }, + { + name: "invalid provider key", + providerKey: "invalid", + providerName: "Name", + supportedTypes: "alipay", + wantErr: true, + errContains: "invalid provider key", + }, + { + name: "empty name", + providerKey: "easypay", + providerName: "", + supportedTypes: "alipay", + wantErr: true, + errContains: "provider name is required", + }, + { + name: "whitespace-only name", + providerKey: "easypay", + providerName: " ", + supportedTypes: "alipay", + wantErr: true, + errContains: "provider name is required", + }, + { + name: "tab-only name", + providerKey: "easypay", + providerName: "\t", + supportedTypes: "alipay", + wantErr: true, + errContains: "provider name is required", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + err := validateProviderRequest(tc.providerKey, tc.providerName, tc.supportedTypes) + if tc.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestIsSensitiveConfigField(t *testing.T) { + t.Parallel() + + tests := []struct { + field string + wantSen bool + }{ + // Sensitive fields (contain key/secret/private/password/pkey patterns) + {"secretKey", true}, + {"apiSecret", true}, + {"pkey", true}, + {"privateKey", true}, + {"apiPassword", true}, + {"appKey", true}, + {"SECRET_TOKEN", true}, + {"PrivateData", true}, + {"PASSWORD", true}, + {"mySecretValue", true}, + + // Non-sensitive fields + {"appId", false}, + {"mchId", false}, + {"apiBase", false}, + {"endpoint", false}, + {"merchantNo", false}, + {"paymentMode", false}, + {"notifyUrl", false}, + } + + for _, tc := range tests { + t.Run(tc.field, func(t *testing.T) { + t.Parallel() + + got := isSensitiveConfigField(tc.field) + assert.Equal(t, tc.wantSen, got, "isSensitiveConfigField(%q)", tc.field) + }) + } +} + +func TestJoinTypes(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input []string + want string + }{ + { + name: "multiple types", + input: []string{"alipay", "wxpay"}, + want: "alipay,wxpay", + }, + { + name: "single type", + input: []string{"stripe"}, + want: "stripe", + }, + { + name: "empty slice", + input: []string{}, + want: "", + }, + { + name: "nil slice", + input: nil, + want: "", + }, + { + name: "three types", + input: []string{"alipay", "wxpay", "stripe"}, + want: "alipay,wxpay,stripe", + }, + { + name: "types with spaces are not trimmed", + input: []string{" alipay ", " wxpay "}, + want: " alipay , wxpay ", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := joinTypes(tc.input) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/backend/internal/service/payment_config_service.go b/backend/internal/service/payment_config_service.go new file mode 100644 index 0000000000000000000000000000000000000000..9042c3aba7a77542da89cc84925fffb36e500f62 --- /dev/null +++ b/backend/internal/service/payment_config_service.go @@ -0,0 +1,351 @@ +package service + +import ( + "context" + "fmt" + "strconv" + "strings" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +const ( + SettingPaymentEnabled = "payment_enabled" + SettingMinRechargeAmount = "MIN_RECHARGE_AMOUNT" + SettingMaxRechargeAmount = "MAX_RECHARGE_AMOUNT" + SettingDailyRechargeLimit = "DAILY_RECHARGE_LIMIT" + SettingOrderTimeoutMinutes = "ORDER_TIMEOUT_MINUTES" + SettingMaxPendingOrders = "MAX_PENDING_ORDERS" + SettingEnabledPaymentTypes = "ENABLED_PAYMENT_TYPES" + SettingLoadBalanceStrategy = "LOAD_BALANCE_STRATEGY" + SettingBalancePayDisabled = "BALANCE_PAYMENT_DISABLED" + SettingProductNamePrefix = "PRODUCT_NAME_PREFIX" + SettingProductNameSuffix = "PRODUCT_NAME_SUFFIX" + SettingHelpImageURL = "PAYMENT_HELP_IMAGE_URL" + SettingHelpText = "PAYMENT_HELP_TEXT" + SettingCancelRateLimitOn = "CANCEL_RATE_LIMIT_ENABLED" + SettingCancelRateLimitMax = "CANCEL_RATE_LIMIT_MAX" + SettingCancelWindowSize = "CANCEL_RATE_LIMIT_WINDOW" + SettingCancelWindowUnit = "CANCEL_RATE_LIMIT_UNIT" + SettingCancelWindowMode = "CANCEL_RATE_LIMIT_WINDOW_MODE" +) + +// Default values for payment configuration settings. +const ( + defaultOrderTimeoutMin = 30 + defaultMaxPendingOrders = 3 +) + +// PaymentConfig holds the payment system configuration. +type PaymentConfig struct { + Enabled bool `json:"enabled"` + MinAmount float64 `json:"min_amount"` + MaxAmount float64 `json:"max_amount"` + DailyLimit float64 `json:"daily_limit"` + OrderTimeoutMin int `json:"order_timeout_minutes"` + MaxPendingOrders int `json:"max_pending_orders"` + EnabledTypes []string `json:"enabled_payment_types"` + BalanceDisabled bool `json:"balance_disabled"` + LoadBalanceStrategy string `json:"load_balance_strategy"` + ProductNamePrefix string `json:"product_name_prefix"` + ProductNameSuffix string `json:"product_name_suffix"` + HelpImageURL string `json:"help_image_url"` + HelpText string `json:"help_text"` + StripePublishableKey string `json:"stripe_publishable_key,omitempty"` + + // Cancel rate limit settings + CancelRateLimitEnabled bool `json:"cancel_rate_limit_enabled"` + CancelRateLimitMax int `json:"cancel_rate_limit_max"` + CancelRateLimitWindow int `json:"cancel_rate_limit_window"` + CancelRateLimitUnit string `json:"cancel_rate_limit_unit"` + CancelRateLimitMode string `json:"cancel_rate_limit_window_mode"` +} + +// UpdatePaymentConfigRequest contains fields to update payment configuration. +type UpdatePaymentConfigRequest struct { + Enabled *bool `json:"enabled"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + DailyLimit *float64 `json:"daily_limit"` + OrderTimeoutMin *int `json:"order_timeout_minutes"` + MaxPendingOrders *int `json:"max_pending_orders"` + EnabledTypes []string `json:"enabled_payment_types"` + BalanceDisabled *bool `json:"balance_disabled"` + LoadBalanceStrategy *string `json:"load_balance_strategy"` + ProductNamePrefix *string `json:"product_name_prefix"` + ProductNameSuffix *string `json:"product_name_suffix"` + HelpImageURL *string `json:"help_image_url"` + HelpText *string `json:"help_text"` + + // Cancel rate limit settings + CancelRateLimitEnabled *bool `json:"cancel_rate_limit_enabled"` + CancelRateLimitMax *int `json:"cancel_rate_limit_max"` + CancelRateLimitWindow *int `json:"cancel_rate_limit_window"` + CancelRateLimitUnit *string `json:"cancel_rate_limit_unit"` + CancelRateLimitMode *string `json:"cancel_rate_limit_window_mode"` +} + +// MethodLimits holds per-payment-type limits. +type MethodLimits struct { + PaymentType string `json:"payment_type"` + FeeRate float64 `json:"fee_rate"` + DailyLimit float64 `json:"daily_limit"` + SingleMin float64 `json:"single_min"` + SingleMax float64 `json:"single_max"` +} + +// MethodLimitsResponse is the full response for the user-facing /limits API. +// It includes per-method limits and the global widest range (union of all methods). +type MethodLimitsResponse struct { + Methods map[string]MethodLimits `json:"methods"` + GlobalMin float64 `json:"global_min"` // 0 = no minimum + GlobalMax float64 `json:"global_max"` // 0 = no maximum +} + +type CreateProviderInstanceRequest struct { + ProviderKey string `json:"provider_key"` + Name string `json:"name"` + Config map[string]string `json:"config"` + SupportedTypes []string `json:"supported_types"` + Enabled bool `json:"enabled"` + PaymentMode string `json:"payment_mode"` + SortOrder int `json:"sort_order"` + Limits string `json:"limits"` + RefundEnabled bool `json:"refund_enabled"` +} + +type UpdateProviderInstanceRequest struct { + Name *string `json:"name"` + Config map[string]string `json:"config"` + SupportedTypes []string `json:"supported_types"` + Enabled *bool `json:"enabled"` + PaymentMode *string `json:"payment_mode"` + SortOrder *int `json:"sort_order"` + Limits *string `json:"limits"` + RefundEnabled *bool `json:"refund_enabled"` +} +type CreatePlanRequest struct { + GroupID int64 `json:"group_id"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + OriginalPrice *float64 `json:"original_price"` + ValidityDays int `json:"validity_days"` + ValidityUnit string `json:"validity_unit"` + Features string `json:"features"` + ProductName string `json:"product_name"` + ForSale bool `json:"for_sale"` + SortOrder int `json:"sort_order"` +} + +type UpdatePlanRequest struct { + GroupID *int64 `json:"group_id"` + Name *string `json:"name"` + Description *string `json:"description"` + Price *float64 `json:"price"` + OriginalPrice *float64 `json:"original_price"` + ValidityDays *int `json:"validity_days"` + ValidityUnit *string `json:"validity_unit"` + Features *string `json:"features"` + ProductName *string `json:"product_name"` + ForSale *bool `json:"for_sale"` + SortOrder *int `json:"sort_order"` +} + +// PaymentConfigService manages payment configuration and CRUD for +// provider instances, channels, and subscription plans. +type PaymentConfigService struct { + entClient *dbent.Client + settingRepo SettingRepository + encryptionKey []byte +} + +// NewPaymentConfigService creates a new PaymentConfigService. +func NewPaymentConfigService(entClient *dbent.Client, settingRepo SettingRepository, encryptionKey []byte) *PaymentConfigService { + return &PaymentConfigService{entClient: entClient, settingRepo: settingRepo, encryptionKey: encryptionKey} +} + +// IsPaymentEnabled returns whether the payment system is enabled. +func (s *PaymentConfigService) IsPaymentEnabled(ctx context.Context) bool { + val, err := s.settingRepo.GetValue(ctx, SettingPaymentEnabled) + if err != nil { + return false + } + return val == "true" +} + +// GetPaymentConfig returns the full payment configuration. +func (s *PaymentConfigService) GetPaymentConfig(ctx context.Context) (*PaymentConfig, error) { + keys := []string{ + SettingPaymentEnabled, SettingMinRechargeAmount, SettingMaxRechargeAmount, + SettingDailyRechargeLimit, SettingOrderTimeoutMinutes, SettingMaxPendingOrders, + SettingEnabledPaymentTypes, SettingBalancePayDisabled, SettingLoadBalanceStrategy, + SettingProductNamePrefix, SettingProductNameSuffix, + SettingHelpImageURL, SettingHelpText, + SettingCancelRateLimitOn, SettingCancelRateLimitMax, + SettingCancelWindowSize, SettingCancelWindowUnit, SettingCancelWindowMode, + } + vals, err := s.settingRepo.GetMultiple(ctx, keys) + if err != nil { + return nil, fmt.Errorf("get payment config settings: %w", err) + } + cfg := s.parsePaymentConfig(vals) + // Load Stripe publishable key from the first enabled Stripe provider instance + cfg.StripePublishableKey = s.getStripePublishableKey(ctx) + return cfg, nil +} + +func (s *PaymentConfigService) parsePaymentConfig(vals map[string]string) *PaymentConfig { + cfg := &PaymentConfig{ + Enabled: vals[SettingPaymentEnabled] == "true", + MinAmount: pcParseFloat(vals[SettingMinRechargeAmount], 1), + MaxAmount: pcParseFloat(vals[SettingMaxRechargeAmount], 0), + DailyLimit: pcParseFloat(vals[SettingDailyRechargeLimit], 0), + OrderTimeoutMin: pcParseInt(vals[SettingOrderTimeoutMinutes], defaultOrderTimeoutMin), + MaxPendingOrders: pcParseInt(vals[SettingMaxPendingOrders], defaultMaxPendingOrders), + BalanceDisabled: vals[SettingBalancePayDisabled] == "true", + LoadBalanceStrategy: vals[SettingLoadBalanceStrategy], + ProductNamePrefix: vals[SettingProductNamePrefix], + ProductNameSuffix: vals[SettingProductNameSuffix], + HelpImageURL: vals[SettingHelpImageURL], + HelpText: vals[SettingHelpText], + + CancelRateLimitEnabled: vals[SettingCancelRateLimitOn] == "true", + CancelRateLimitMax: pcParseInt(vals[SettingCancelRateLimitMax], 10), + CancelRateLimitWindow: pcParseInt(vals[SettingCancelWindowSize], 1), + CancelRateLimitUnit: vals[SettingCancelWindowUnit], + CancelRateLimitMode: vals[SettingCancelWindowMode], + } + if cfg.LoadBalanceStrategy == "" { + cfg.LoadBalanceStrategy = payment.DefaultLoadBalanceStrategy + } + if raw := vals[SettingEnabledPaymentTypes]; raw != "" { + for _, t := range strings.Split(raw, ",") { + t = strings.TrimSpace(t) + if t != "" { + cfg.EnabledTypes = append(cfg.EnabledTypes, t) + } + } + } + return cfg +} + +// getStripePublishableKey finds the publishable key from the first enabled Stripe provider instance. +func (s *PaymentConfigService) getStripePublishableKey(ctx context.Context) string { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Where( + paymentproviderinstance.EnabledEQ(true), + paymentproviderinstance.ProviderKeyEQ(payment.TypeStripe), + ).Limit(1).All(ctx) + if err != nil || len(instances) == 0 { + return "" + } + cfg, err := s.decryptConfig(instances[0].Config) + if err != nil || cfg == nil { + return "" + } + return cfg[payment.ConfigKeyPublishableKey] +} + +// UpdatePaymentConfig updates the payment configuration settings. +// NOTE: This function exceeds 30 lines because each field requires an independent +// nil-check before serialisation — this is inherent to patch-style update patterns +// and cannot be meaningfully decomposed without introducing unnecessary abstraction. +func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req UpdatePaymentConfigRequest) error { + m := map[string]string{ + SettingPaymentEnabled: formatBoolOrEmpty(req.Enabled), + SettingMinRechargeAmount: formatPositiveFloat(req.MinAmount), + SettingMaxRechargeAmount: formatPositiveFloat(req.MaxAmount), + SettingDailyRechargeLimit: formatPositiveFloat(req.DailyLimit), + SettingOrderTimeoutMinutes: formatPositiveInt(req.OrderTimeoutMin), + SettingMaxPendingOrders: formatPositiveInt(req.MaxPendingOrders), + SettingBalancePayDisabled: formatBoolOrEmpty(req.BalanceDisabled), + SettingLoadBalanceStrategy: derefStr(req.LoadBalanceStrategy), + SettingProductNamePrefix: derefStr(req.ProductNamePrefix), + SettingProductNameSuffix: derefStr(req.ProductNameSuffix), + SettingHelpImageURL: derefStr(req.HelpImageURL), + SettingHelpText: derefStr(req.HelpText), + SettingCancelRateLimitOn: formatBoolOrEmpty(req.CancelRateLimitEnabled), + SettingCancelRateLimitMax: formatPositiveInt(req.CancelRateLimitMax), + SettingCancelWindowSize: formatPositiveInt(req.CancelRateLimitWindow), + SettingCancelWindowUnit: derefStr(req.CancelRateLimitUnit), + SettingCancelWindowMode: derefStr(req.CancelRateLimitMode), + } + if req.EnabledTypes != nil { + m[SettingEnabledPaymentTypes] = strings.Join(req.EnabledTypes, ",") + } else { + m[SettingEnabledPaymentTypes] = "" + } + return s.settingRepo.SetMultiple(ctx, m) +} + +func formatBoolOrEmpty(v *bool) string { + if v == nil { + return "" + } + return strconv.FormatBool(*v) +} + +func formatPositiveFloat(v *float64) string { + if v == nil || *v <= 0 { + return "" // empty → parsePaymentConfig uses default + } + return strconv.FormatFloat(*v, 'f', 2, 64) +} + +func formatPositiveInt(v *int) string { + if v == nil || *v <= 0 { + return "" + } + return strconv.Itoa(*v) +} + +func derefStr(v *string) string { + if v == nil { + return "" + } + return *v +} + +func splitTypes(s string) []string { + if s == "" { + return nil + } + parts := strings.Split(s, ",") + result := make([]string, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + if p != "" { + result = append(result, p) + } + } + return result +} + +func joinTypes(types []string) string { + return strings.Join(types, ",") +} + +func pcParseFloat(s string, defaultVal float64) float64 { + if s == "" { + return defaultVal + } + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return defaultVal + } + return v +} + +func pcParseInt(s string, defaultVal int) int { + if s == "" { + return defaultVal + } + v, err := strconv.Atoi(s) + if err != nil { + return defaultVal + } + return v +} diff --git a/backend/internal/service/payment_config_service_test.go b/backend/internal/service/payment_config_service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..027bb796fcde5f6dbae4d46c7dfc3ee7d2c1e99e --- /dev/null +++ b/backend/internal/service/payment_config_service_test.go @@ -0,0 +1,206 @@ +package service + +import ( + "testing" + + "github.com/Wei-Shaw/sub2api/internal/payment" +) + +func TestPcParseFloat(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + defaultVal float64 + expected float64 + }{ + {"empty string returns default", "", 1.0, 1.0}, + {"valid float", "3.14", 0, 3.14}, + {"valid integer as float", "42", 0, 42.0}, + {"invalid string returns default", "notanumber", 9.99, 9.99}, + {"zero value", "0", 5.0, 0}, + {"negative value", "-10.5", 0, -10.5}, + {"very large value", "99999999.99", 0, 99999999.99}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := pcParseFloat(tt.input, tt.defaultVal) + if got != tt.expected { + t.Fatalf("pcParseFloat(%q, %v) = %v, want %v", tt.input, tt.defaultVal, got, tt.expected) + } + }) + } +} + +func TestPcParseInt(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + defaultVal int + expected int + }{ + {"empty string returns default", "", 30, 30}, + {"valid int", "10", 0, 10}, + {"invalid string returns default", "abc", 5, 5}, + {"float string returns default", "3.14", 0, 0}, + {"zero value", "0", 99, 0}, + {"negative value", "-1", 0, -1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := pcParseInt(tt.input, tt.defaultVal) + if got != tt.expected { + t.Fatalf("pcParseInt(%q, %v) = %v, want %v", tt.input, tt.defaultVal, got, tt.expected) + } + }) + } +} + +func TestParsePaymentConfig(t *testing.T) { + t.Parallel() + + svc := &PaymentConfigService{} + + t.Run("empty vals uses defaults", func(t *testing.T) { + t.Parallel() + cfg := svc.parsePaymentConfig(map[string]string{}) + if cfg.Enabled { + t.Fatal("expected Enabled=false by default") + } + if cfg.MinAmount != 1 { + t.Fatalf("expected MinAmount=1, got %v", cfg.MinAmount) + } + if cfg.MaxAmount != 0 { + t.Fatalf("expected MaxAmount=0 (no limit), got %v", cfg.MaxAmount) + } + if cfg.OrderTimeoutMin != 30 { + t.Fatalf("expected OrderTimeoutMin=30, got %v", cfg.OrderTimeoutMin) + } + if cfg.MaxPendingOrders != 3 { + t.Fatalf("expected MaxPendingOrders=3, got %v", cfg.MaxPendingOrders) + } + if cfg.LoadBalanceStrategy != payment.DefaultLoadBalanceStrategy { + t.Fatalf("expected LoadBalanceStrategy=%s, got %q", payment.DefaultLoadBalanceStrategy, cfg.LoadBalanceStrategy) + } + if len(cfg.EnabledTypes) != 0 { + t.Fatalf("expected empty EnabledTypes, got %v", cfg.EnabledTypes) + } + }) + + t.Run("all values populated", func(t *testing.T) { + t.Parallel() + vals := map[string]string{ + SettingPaymentEnabled: "true", + SettingMinRechargeAmount: "5.00", + SettingMaxRechargeAmount: "1000.00", + SettingDailyRechargeLimit: "5000.00", + SettingOrderTimeoutMinutes: "15", + SettingMaxPendingOrders: "5", + SettingEnabledPaymentTypes: "alipay,wxpay,stripe", + SettingBalancePayDisabled: "true", + SettingLoadBalanceStrategy: "least_amount", + SettingProductNamePrefix: "PRE", + SettingProductNameSuffix: "SUF", + } + cfg := svc.parsePaymentConfig(vals) + + if !cfg.Enabled { + t.Fatal("expected Enabled=true") + } + if cfg.MinAmount != 5 { + t.Fatalf("MinAmount = %v, want 5", cfg.MinAmount) + } + if cfg.MaxAmount != 1000 { + t.Fatalf("MaxAmount = %v, want 1000", cfg.MaxAmount) + } + if cfg.DailyLimit != 5000 { + t.Fatalf("DailyLimit = %v, want 5000", cfg.DailyLimit) + } + if cfg.OrderTimeoutMin != 15 { + t.Fatalf("OrderTimeoutMin = %v, want 15", cfg.OrderTimeoutMin) + } + if cfg.MaxPendingOrders != 5 { + t.Fatalf("MaxPendingOrders = %v, want 5", cfg.MaxPendingOrders) + } + if len(cfg.EnabledTypes) != 3 { + t.Fatalf("EnabledTypes len = %d, want 3", len(cfg.EnabledTypes)) + } + if cfg.EnabledTypes[0] != "alipay" || cfg.EnabledTypes[1] != "wxpay" || cfg.EnabledTypes[2] != "stripe" { + t.Fatalf("EnabledTypes = %v, want [alipay wxpay stripe]", cfg.EnabledTypes) + } + if !cfg.BalanceDisabled { + t.Fatal("expected BalanceDisabled=true") + } + if cfg.LoadBalanceStrategy != "least_amount" { + t.Fatalf("LoadBalanceStrategy = %q, want %q", cfg.LoadBalanceStrategy, "least_amount") + } + if cfg.ProductNamePrefix != "PRE" { + t.Fatalf("ProductNamePrefix = %q, want %q", cfg.ProductNamePrefix, "PRE") + } + if cfg.ProductNameSuffix != "SUF" { + t.Fatalf("ProductNameSuffix = %q, want %q", cfg.ProductNameSuffix, "SUF") + } + }) + + t.Run("enabled types with spaces are trimmed", func(t *testing.T) { + t.Parallel() + vals := map[string]string{ + SettingEnabledPaymentTypes: " alipay , wxpay ", + } + cfg := svc.parsePaymentConfig(vals) + if len(cfg.EnabledTypes) != 2 { + t.Fatalf("EnabledTypes len = %d, want 2", len(cfg.EnabledTypes)) + } + if cfg.EnabledTypes[0] != "alipay" || cfg.EnabledTypes[1] != "wxpay" { + t.Fatalf("EnabledTypes = %v, want [alipay wxpay]", cfg.EnabledTypes) + } + }) + + t.Run("empty enabled types string", func(t *testing.T) { + t.Parallel() + vals := map[string]string{ + SettingEnabledPaymentTypes: "", + } + cfg := svc.parsePaymentConfig(vals) + if len(cfg.EnabledTypes) != 0 { + t.Fatalf("expected empty EnabledTypes for empty string, got %v", cfg.EnabledTypes) + } + }) +} + +func TestGetBasePaymentType(t *testing.T) { + t.Parallel() + + tests := []struct { + input string + expected string + }{ + {payment.TypeEasyPay, payment.TypeEasyPay}, + {payment.TypeStripe, payment.TypeStripe}, + {payment.TypeCard, payment.TypeStripe}, + {payment.TypeLink, payment.TypeStripe}, + {payment.TypeAlipay, payment.TypeAlipay}, + {payment.TypeAlipayDirect, payment.TypeAlipay}, + {payment.TypeWxpay, payment.TypeWxpay}, + {payment.TypeWxpayDirect, payment.TypeWxpay}, + {"unknown", "unknown"}, + {"", ""}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + t.Parallel() + got := payment.GetBasePaymentType(tt.input) + if got != tt.expected { + t.Fatalf("GetBasePaymentType(%q) = %q, want %q", tt.input, got, tt.expected) + } + }) + } +} diff --git a/backend/internal/service/payment_fulfillment.go b/backend/internal/service/payment_fulfillment.go new file mode 100644 index 0000000000000000000000000000000000000000..de41d7426164d389bdca5cfc31c9216d45fdebcc --- /dev/null +++ b/backend/internal/service/payment_fulfillment.go @@ -0,0 +1,325 @@ +package service + +import ( + "context" + "fmt" + "log/slog" + "math" + "strconv" + "strings" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/internal/payment" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// --- Payment Notification & Fulfillment --- + +func (s *PaymentService) HandlePaymentNotification(ctx context.Context, n *payment.PaymentNotification, pk string) error { + if n.Status != payment.NotificationStatusSuccess { + return nil + } + // Look up order by out_trade_no (the external order ID we sent to the provider) + order, err := s.entClient.PaymentOrder.Query().Where(paymentorder.OutTradeNo(n.OrderID)).Only(ctx) + if err != nil { + // Fallback: try legacy format (sub2_N where N is DB ID) + trimmed := strings.TrimPrefix(n.OrderID, orderIDPrefix) + if oid, parseErr := strconv.ParseInt(trimmed, 10, 64); parseErr == nil { + return s.confirmPayment(ctx, oid, n.TradeNo, n.Amount, pk) + } + return fmt.Errorf("order not found for out_trade_no: %s", n.OrderID) + } + return s.confirmPayment(ctx, order.ID, n.TradeNo, n.Amount, pk) +} + +func (s *PaymentService) confirmPayment(ctx context.Context, oid int64, tradeNo string, paid float64, pk string) error { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + slog.Error("order not found", "orderID", oid) + return nil + } + // Skip amount check when paid=0 (e.g. QueryOrder doesn't return amount). + // Also skip if paid is NaN/Inf (malformed provider data). + if paid > 0 && !math.IsNaN(paid) && !math.IsInf(paid, 0) { + if math.Abs(paid-o.PayAmount) > amountToleranceCNY { + s.writeAuditLog(ctx, o.ID, "PAYMENT_AMOUNT_MISMATCH", pk, map[string]any{"expected": o.PayAmount, "paid": paid, "tradeNo": tradeNo}) + return fmt.Errorf("amount mismatch: expected %.2f, got %.2f", o.PayAmount, paid) + } + } + // Use order's expected amount when provider didn't report one + if paid <= 0 || math.IsNaN(paid) || math.IsInf(paid, 0) { + paid = o.PayAmount + } + return s.toPaid(ctx, o, tradeNo, paid, pk) +} + +func (s *PaymentService) toPaid(ctx context.Context, o *dbent.PaymentOrder, tradeNo string, paid float64, pk string) error { + previousStatus := o.Status + now := time.Now() + grace := now.Add(-paymentGraceMinutes * time.Minute) + c, err := s.entClient.PaymentOrder.Update().Where( + paymentorder.IDEQ(o.ID), + paymentorder.Or( + paymentorder.StatusEQ(OrderStatusPending), + paymentorder.StatusEQ(OrderStatusCancelled), + paymentorder.And( + paymentorder.StatusEQ(OrderStatusExpired), + paymentorder.UpdatedAtGTE(grace), + ), + ), + ).SetStatus(OrderStatusPaid).SetPayAmount(paid).SetPaymentTradeNo(tradeNo).SetPaidAt(now).ClearFailedAt().ClearFailedReason().Save(ctx) + if err != nil { + return fmt.Errorf("update to PAID: %w", err) + } + if c == 0 { + return s.alreadyProcessed(ctx, o) + } + if previousStatus == OrderStatusCancelled || previousStatus == OrderStatusExpired { + slog.Info("order recovered from webhook payment success", + "orderID", o.ID, + "previousStatus", previousStatus, + "tradeNo", tradeNo, + "provider", pk, + ) + s.writeAuditLog(ctx, o.ID, "ORDER_RECOVERED", pk, map[string]any{ + "previous_status": previousStatus, + "tradeNo": tradeNo, + "paidAmount": paid, + "reason": "webhook payment success received after order " + previousStatus, + }) + } + s.writeAuditLog(ctx, o.ID, "ORDER_PAID", pk, map[string]any{"tradeNo": tradeNo, "paidAmount": paid}) + return s.executeFulfillment(ctx, o.ID) +} + +func (s *PaymentService) alreadyProcessed(ctx context.Context, o *dbent.PaymentOrder) error { + cur, err := s.entClient.PaymentOrder.Get(ctx, o.ID) + if err != nil { + return nil + } + switch cur.Status { + case OrderStatusCompleted, OrderStatusRefunded: + return nil + case OrderStatusFailed: + return s.executeFulfillment(ctx, o.ID) + case OrderStatusPaid, OrderStatusRecharging: + return fmt.Errorf("order %d is being processed", o.ID) + case OrderStatusExpired: + slog.Warn("webhook payment success for expired order beyond grace period", + "orderID", o.ID, + "status", cur.Status, + "updatedAt", cur.UpdatedAt, + ) + s.writeAuditLog(ctx, o.ID, "PAYMENT_AFTER_EXPIRY", "system", map[string]any{ + "status": cur.Status, + "updatedAt": cur.UpdatedAt, + "reason": "payment arrived after expiry grace period", + }) + return nil + default: + return nil + } +} + +func (s *PaymentService) executeFulfillment(ctx context.Context, oid int64) error { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + return fmt.Errorf("get order: %w", err) + } + if o.OrderType == payment.OrderTypeSubscription { + return s.ExecuteSubscriptionFulfillment(ctx, oid) + } + return s.ExecuteBalanceFulfillment(ctx, oid) +} + +func (s *PaymentService) ExecuteBalanceFulfillment(ctx context.Context, oid int64) error { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + return infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.Status == OrderStatusCompleted { + return nil + } + if psIsRefundStatus(o.Status) { + return infraerrors.BadRequest("INVALID_STATUS", "refund-related order cannot fulfill") + } + if o.Status != OrderStatusPaid && o.Status != OrderStatusFailed { + return infraerrors.BadRequest("INVALID_STATUS", "order cannot fulfill in status "+o.Status) + } + c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.StatusIn(OrderStatusPaid, OrderStatusFailed)).SetStatus(OrderStatusRecharging).Save(ctx) + if err != nil { + return fmt.Errorf("lock: %w", err) + } + if c == 0 { + return nil + } + if err := s.doBalance(ctx, o); err != nil { + s.markFailed(ctx, oid, err) + return err + } + return nil +} + +// redeemAction represents the idempotency decision for balance fulfillment. +type redeemAction int + +const ( + // redeemActionCreate: code does not exist — create it, then redeem. + redeemActionCreate redeemAction = iota + // redeemActionRedeem: code exists but is unused — skip creation, redeem only. + redeemActionRedeem + // redeemActionSkipCompleted: code exists and is already used — skip to mark completed. + redeemActionSkipCompleted +) + +// resolveRedeemAction decides the idempotency action based on an existing redeem code lookup. +// existing is the result of GetByCode; lookupErr is the error from that call. +func resolveRedeemAction(existing *RedeemCode, lookupErr error) redeemAction { + if existing == nil || lookupErr != nil { + return redeemActionCreate + } + if existing.IsUsed() { + return redeemActionSkipCompleted + } + return redeemActionRedeem +} + +func (s *PaymentService) doBalance(ctx context.Context, o *dbent.PaymentOrder) error { + // Idempotency: check if redeem code already exists (from a previous partial run) + existing, lookupErr := s.redeemService.GetByCode(ctx, o.RechargeCode) + action := resolveRedeemAction(existing, lookupErr) + + switch action { + case redeemActionSkipCompleted: + // Code already created and redeemed — just mark completed + return s.markCompleted(ctx, o, "RECHARGE_SUCCESS") + case redeemActionCreate: + rc := &RedeemCode{Code: o.RechargeCode, Type: RedeemTypeBalance, Value: o.Amount, Status: StatusUnused} + if err := s.redeemService.CreateCode(ctx, rc); err != nil { + return fmt.Errorf("create redeem code: %w", err) + } + case redeemActionRedeem: + // Code exists but unused — skip creation, proceed to redeem + } + if _, err := s.redeemService.Redeem(ctx, o.UserID, o.RechargeCode); err != nil { + return fmt.Errorf("redeem balance: %w", err) + } + return s.markCompleted(ctx, o, "RECHARGE_SUCCESS") +} + +func (s *PaymentService) markCompleted(ctx context.Context, o *dbent.PaymentOrder, auditAction string) error { + now := time.Now() + _, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(o.ID), paymentorder.StatusEQ(OrderStatusRecharging)).SetStatus(OrderStatusCompleted).SetCompletedAt(now).Save(ctx) + if err != nil { + return fmt.Errorf("mark completed: %w", err) + } + s.writeAuditLog(ctx, o.ID, auditAction, "system", map[string]any{"rechargeCode": o.RechargeCode, "amount": o.Amount}) + return nil +} + +func (s *PaymentService) ExecuteSubscriptionFulfillment(ctx context.Context, oid int64) error { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + return infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.Status == OrderStatusCompleted { + return nil + } + if psIsRefundStatus(o.Status) { + return infraerrors.BadRequest("INVALID_STATUS", "refund-related order cannot fulfill") + } + if o.Status != OrderStatusPaid && o.Status != OrderStatusFailed { + return infraerrors.BadRequest("INVALID_STATUS", "order cannot fulfill in status "+o.Status) + } + if o.SubscriptionGroupID == nil || o.SubscriptionDays == nil { + return infraerrors.BadRequest("INVALID_STATUS", "missing subscription info") + } + c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.StatusIn(OrderStatusPaid, OrderStatusFailed)).SetStatus(OrderStatusRecharging).Save(ctx) + if err != nil { + return fmt.Errorf("lock: %w", err) + } + if c == 0 { + return nil + } + if err := s.doSub(ctx, o); err != nil { + s.markFailed(ctx, oid, err) + return err + } + return nil +} + +func (s *PaymentService) doSub(ctx context.Context, o *dbent.PaymentOrder) error { + gid := *o.SubscriptionGroupID + days := *o.SubscriptionDays + g, err := s.groupRepo.GetByID(ctx, gid) + if err != nil || g.Status != payment.EntityStatusActive { + return fmt.Errorf("group %d no longer exists or inactive", gid) + } + // Idempotency: check audit log to see if subscription was already assigned. + // Prevents double-extension on retry after markCompleted fails. + if s.hasAuditLog(ctx, o.ID, "SUBSCRIPTION_SUCCESS") { + slog.Info("subscription already assigned for order, skipping", "orderID", o.ID, "groupID", gid) + return s.markCompleted(ctx, o, "SUBSCRIPTION_SUCCESS") + } + orderNote := fmt.Sprintf("payment order %d", o.ID) + _, _, err = s.subscriptionSvc.AssignOrExtendSubscription(ctx, &AssignSubscriptionInput{UserID: o.UserID, GroupID: gid, ValidityDays: days, AssignedBy: 0, Notes: orderNote}) + if err != nil { + return fmt.Errorf("assign subscription: %w", err) + } + return s.markCompleted(ctx, o, "SUBSCRIPTION_SUCCESS") +} + +func (s *PaymentService) hasAuditLog(ctx context.Context, orderID int64, action string) bool { + oid := strconv.FormatInt(orderID, 10) + c, _ := s.entClient.PaymentAuditLog.Query(). + Where(paymentauditlog.OrderIDEQ(oid), paymentauditlog.ActionEQ(action)). + Limit(1).Count(ctx) + return c > 0 +} + +func (s *PaymentService) markFailed(ctx context.Context, oid int64, cause error) { + now := time.Now() + r := psErrMsg(cause) + // Only mark FAILED if still in RECHARGING state — prevents overwriting + // a COMPLETED order when markCompleted failed but fulfillment succeeded. + c, e := s.entClient.PaymentOrder.Update(). + Where(paymentorder.IDEQ(oid), paymentorder.StatusEQ(OrderStatusRecharging)). + SetStatus(OrderStatusFailed).SetFailedAt(now).SetFailedReason(r).Save(ctx) + if e != nil { + slog.Error("mark FAILED", "orderID", oid, "error", e) + } + if c > 0 { + s.writeAuditLog(ctx, oid, "FULFILLMENT_FAILED", "system", map[string]any{"reason": r}) + } +} + +func (s *PaymentService) RetryFulfillment(ctx context.Context, oid int64) error { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + return infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.PaidAt == nil { + return infraerrors.BadRequest("INVALID_STATUS", "order is not paid") + } + if psIsRefundStatus(o.Status) { + return infraerrors.BadRequest("INVALID_STATUS", "refund-related order cannot retry") + } + if o.Status == OrderStatusRecharging { + return infraerrors.Conflict("CONFLICT", "order is being processed") + } + if o.Status == OrderStatusCompleted { + return infraerrors.BadRequest("INVALID_STATUS", "order already completed") + } + if o.Status != OrderStatusFailed && o.Status != OrderStatusPaid { + return infraerrors.BadRequest("INVALID_STATUS", "only paid and failed orders can retry") + } + _, err = s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.StatusIn(OrderStatusFailed, OrderStatusPaid)).SetStatus(OrderStatusPaid).ClearFailedAt().ClearFailedReason().Save(ctx) + if err != nil { + return fmt.Errorf("reset for retry: %w", err) + } + s.writeAuditLog(ctx, oid, "RECHARGE_RETRY", "admin", map[string]any{"detail": "admin manual retry"}) + return s.executeFulfillment(ctx, oid) +} diff --git a/backend/internal/service/payment_fulfillment_test.go b/backend/internal/service/payment_fulfillment_test.go new file mode 100644 index 0000000000000000000000000000000000000000..625b0d9f0ed9bed88489abc63bd7b5a5c24b6eb7 --- /dev/null +++ b/backend/internal/service/payment_fulfillment_test.go @@ -0,0 +1,163 @@ +//go:build unit + +package service + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +// --------------------------------------------------------------------------- +// resolveRedeemAction — pure idempotency decision logic +// --------------------------------------------------------------------------- + +func TestResolveRedeemAction_CodeNotFound(t *testing.T) { + t.Parallel() + action := resolveRedeemAction(nil, nil) + assert.Equal(t, redeemActionCreate, action, "nil code with nil error should create") +} + +func TestResolveRedeemAction_LookupError(t *testing.T) { + t.Parallel() + action := resolveRedeemAction(nil, errors.New("db connection lost")) + assert.Equal(t, redeemActionCreate, action, "lookup error should fall back to create") +} + +func TestResolveRedeemAction_LookupErrorWithNonNilCode(t *testing.T) { + t.Parallel() + // Edge case: both code and error are non-nil (shouldn't happen in practice, + // but the function should still treat error as authoritative) + code := &RedeemCode{Status: StatusUnused} + action := resolveRedeemAction(code, errors.New("partial error")) + assert.Equal(t, redeemActionCreate, action, "non-nil error should always result in create regardless of code") +} + +func TestResolveRedeemAction_CodeExistsAndUsed(t *testing.T) { + t.Parallel() + code := &RedeemCode{ + Code: "test-code-123", + Status: StatusUsed, + Type: RedeemTypeBalance, + Value: 10.0, + } + action := resolveRedeemAction(code, nil) + assert.Equal(t, redeemActionSkipCompleted, action, "used code should skip to completed") +} + +func TestResolveRedeemAction_CodeExistsAndUnused(t *testing.T) { + t.Parallel() + code := &RedeemCode{ + Code: "test-code-456", + Status: StatusUnused, + Type: RedeemTypeBalance, + Value: 25.0, + } + action := resolveRedeemAction(code, nil) + assert.Equal(t, redeemActionRedeem, action, "unused code should skip creation and proceed to redeem") +} + +func TestResolveRedeemAction_CodeExistsWithExpiredStatus(t *testing.T) { + t.Parallel() + // A code with a non-standard status (neither "unused" nor "used") + // should NOT be treated as used, so it falls through to redeemActionRedeem. + code := &RedeemCode{ + Code: "expired-code", + Status: StatusExpired, + } + action := resolveRedeemAction(code, nil) + assert.Equal(t, redeemActionRedeem, action, "expired-status code is not IsUsed(), should redeem") +} + +// --------------------------------------------------------------------------- +// Table-driven comprehensive test +// --------------------------------------------------------------------------- + +func TestResolveRedeemAction_Table(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code *RedeemCode + err error + expected redeemAction + }{ + { + name: "nil code, nil error — first run", + code: nil, + err: nil, + expected: redeemActionCreate, + }, + { + name: "nil code, lookup error — treat as not found", + code: nil, + err: ErrRedeemCodeNotFound, + expected: redeemActionCreate, + }, + { + name: "nil code, generic DB error — treat as not found", + code: nil, + err: errors.New("connection refused"), + expected: redeemActionCreate, + }, + { + name: "code exists, used — previous run completed redeem", + code: &RedeemCode{Status: StatusUsed}, + err: nil, + expected: redeemActionSkipCompleted, + }, + { + name: "code exists, unused — previous run created code but crashed before redeem", + code: &RedeemCode{Status: StatusUnused}, + err: nil, + expected: redeemActionRedeem, + }, + { + name: "code exists but error also set — error takes precedence", + code: &RedeemCode{Status: StatusUsed}, + err: errors.New("unexpected"), + expected: redeemActionCreate, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := resolveRedeemAction(tt.code, tt.err) + assert.Equal(t, tt.expected, got) + }) + } +} + +// --------------------------------------------------------------------------- +// redeemAction enum value sanity +// --------------------------------------------------------------------------- + +func TestRedeemAction_DistinctValues(t *testing.T) { + t.Parallel() + // Ensure the three actions have distinct values (iota correctness) + assert.NotEqual(t, redeemActionCreate, redeemActionRedeem) + assert.NotEqual(t, redeemActionCreate, redeemActionSkipCompleted) + assert.NotEqual(t, redeemActionRedeem, redeemActionSkipCompleted) +} + +// --------------------------------------------------------------------------- +// RedeemCode.IsUsed / CanUse interaction with resolveRedeemAction +// --------------------------------------------------------------------------- + +func TestResolveRedeemAction_IsUsedCanUseConsistency(t *testing.T) { + t.Parallel() + + usedCode := &RedeemCode{Status: StatusUsed} + unusedCode := &RedeemCode{Status: StatusUnused} + + // Verify our decision function is consistent with the domain model methods + assert.True(t, usedCode.IsUsed()) + assert.False(t, usedCode.CanUse()) + assert.Equal(t, redeemActionSkipCompleted, resolveRedeemAction(usedCode, nil)) + + assert.False(t, unusedCode.IsUsed()) + assert.True(t, unusedCode.CanUse()) + assert.Equal(t, redeemActionRedeem, resolveRedeemAction(unusedCode, nil)) +} diff --git a/backend/internal/service/payment_order.go b/backend/internal/service/payment_order.go new file mode 100644 index 0000000000000000000000000000000000000000..2a952ece100a7491031f8279d0980672ced743f5 --- /dev/null +++ b/backend/internal/service/payment_order.go @@ -0,0 +1,314 @@ +package service + +import ( + "context" + "fmt" + "log/slog" + "math" + "strconv" + "strings" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/Wei-Shaw/sub2api/internal/payment/provider" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// --- Order Creation --- + +func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*CreateOrderResponse, error) { + if req.OrderType == "" { + req.OrderType = payment.OrderTypeBalance + } + cfg, err := s.configService.GetPaymentConfig(ctx) + if err != nil { + return nil, fmt.Errorf("get payment config: %w", err) + } + if !cfg.Enabled { + return nil, infraerrors.Forbidden("PAYMENT_DISABLED", "payment system is disabled") + } + plan, err := s.validateOrderInput(ctx, req, cfg) + if err != nil { + return nil, err + } + if err := s.checkCancelRateLimit(ctx, req.UserID, cfg); err != nil { + return nil, err + } + user, err := s.userRepo.GetByID(ctx, req.UserID) + if err != nil { + return nil, fmt.Errorf("get user: %w", err) + } + if user.Status != payment.EntityStatusActive { + return nil, infraerrors.Forbidden("USER_INACTIVE", "user account is disabled") + } + amount := req.Amount + if plan != nil { + amount = plan.Price + } + feeRate := s.getFeeRate(req.PaymentType) + payAmountStr := payment.CalculatePayAmount(amount, feeRate) + payAmount, _ := strconv.ParseFloat(payAmountStr, 64) + order, err := s.createOrderInTx(ctx, req, user, plan, cfg, amount, feeRate, payAmount) + if err != nil { + return nil, err + } + resp, err := s.invokeProvider(ctx, order, req, cfg, payAmountStr, payAmount, plan) + if err != nil { + _, _ = s.entClient.PaymentOrder.UpdateOneID(order.ID). + SetStatus(OrderStatusFailed). + Save(ctx) + return nil, err + } + return resp, nil +} + +func (s *PaymentService) validateOrderInput(ctx context.Context, req CreateOrderRequest, cfg *PaymentConfig) (*dbent.SubscriptionPlan, error) { + if req.OrderType == payment.OrderTypeBalance && cfg.BalanceDisabled { + return nil, infraerrors.Forbidden("BALANCE_PAYMENT_DISABLED", "balance recharge has been disabled") + } + if req.OrderType == payment.OrderTypeSubscription { + return s.validateSubOrder(ctx, req) + } + if math.IsNaN(req.Amount) || math.IsInf(req.Amount, 0) || req.Amount <= 0 { + return nil, infraerrors.BadRequest("INVALID_AMOUNT", "amount must be a positive number") + } + if (cfg.MinAmount > 0 && req.Amount < cfg.MinAmount) || (cfg.MaxAmount > 0 && req.Amount > cfg.MaxAmount) { + return nil, infraerrors.BadRequest("INVALID_AMOUNT", "amount out of range"). + WithMetadata(map[string]string{"min": fmt.Sprintf("%.2f", cfg.MinAmount), "max": fmt.Sprintf("%.2f", cfg.MaxAmount)}) + } + return nil, nil +} + +func (s *PaymentService) validateSubOrder(ctx context.Context, req CreateOrderRequest) (*dbent.SubscriptionPlan, error) { + if req.PlanID == 0 { + return nil, infraerrors.BadRequest("INVALID_INPUT", "subscription order requires a plan") + } + plan, err := s.configService.GetPlan(ctx, req.PlanID) + if err != nil || !plan.ForSale { + return nil, infraerrors.NotFound("PLAN_NOT_AVAILABLE", "plan not found or not for sale") + } + group, err := s.groupRepo.GetByID(ctx, plan.GroupID) + if err != nil || group.Status != payment.EntityStatusActive { + return nil, infraerrors.NotFound("GROUP_NOT_FOUND", "subscription group is no longer available") + } + if !group.IsSubscriptionType() { + return nil, infraerrors.BadRequest("GROUP_TYPE_MISMATCH", "group is not a subscription type") + } + return plan, nil +} + +func (s *PaymentService) createOrderInTx(ctx context.Context, req CreateOrderRequest, user *User, plan *dbent.SubscriptionPlan, cfg *PaymentConfig, amount, feeRate, payAmount float64) (*dbent.PaymentOrder, error) { + tx, err := s.entClient.Tx(ctx) + if err != nil { + return nil, fmt.Errorf("begin transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + if err := s.checkPendingLimit(ctx, tx, req.UserID, cfg.MaxPendingOrders); err != nil { + return nil, err + } + if err := s.checkDailyLimit(ctx, tx, req.UserID, amount, cfg.DailyLimit); err != nil { + return nil, err + } + tm := cfg.OrderTimeoutMin + if tm <= 0 { + tm = defaultOrderTimeoutMin + } + exp := time.Now().Add(time.Duration(tm) * time.Minute) + b := tx.PaymentOrder.Create(). + SetUserID(req.UserID). + SetUserEmail(user.Email). + SetUserName(user.Username). + SetNillableUserNotes(psNilIfEmpty(user.Notes)). + SetAmount(amount). + SetPayAmount(payAmount). + SetFeeRate(feeRate). + SetRechargeCode(""). + SetOutTradeNo(generateOutTradeNo()). + SetPaymentType(req.PaymentType). + SetPaymentTradeNo(""). + SetOrderType(req.OrderType). + SetStatus(OrderStatusPending). + SetExpiresAt(exp). + SetClientIP(req.ClientIP). + SetSrcHost(req.SrcHost) + if req.SrcURL != "" { + b.SetSrcURL(req.SrcURL) + } + if plan != nil { + b.SetPlanID(plan.ID).SetSubscriptionGroupID(plan.GroupID).SetSubscriptionDays(psComputeValidityDays(plan.ValidityDays, plan.ValidityUnit)) + } + order, err := b.Save(ctx) + if err != nil { + return nil, fmt.Errorf("create order: %w", err) + } + code := fmt.Sprintf("PAY-%d-%d", order.ID, time.Now().UnixNano()%100000) + order, err = tx.PaymentOrder.UpdateOneID(order.ID).SetRechargeCode(code).Save(ctx) + if err != nil { + return nil, fmt.Errorf("set recharge code: %w", err) + } + if err := tx.Commit(); err != nil { + return nil, fmt.Errorf("commit order transaction: %w", err) + } + return order, nil +} + +func (s *PaymentService) checkPendingLimit(ctx context.Context, tx *dbent.Tx, userID int64, max int) error { + if max <= 0 { + max = defaultMaxPendingOrders + } + c, err := tx.PaymentOrder.Query().Where(paymentorder.UserIDEQ(userID), paymentorder.StatusEQ(OrderStatusPending)).Count(ctx) + if err != nil { + return fmt.Errorf("count pending orders: %w", err) + } + if c >= max { + return infraerrors.TooManyRequests("TOO_MANY_PENDING", fmt.Sprintf("too many pending orders (max %d)", max)). + WithMetadata(map[string]string{"max": strconv.Itoa(max)}) + } + return nil +} + +func (s *PaymentService) checkDailyLimit(ctx context.Context, tx *dbent.Tx, userID int64, amount, limit float64) error { + if limit <= 0 { + return nil + } + ts := psStartOfDayUTC(time.Now()) + orders, err := tx.PaymentOrder.Query().Where(paymentorder.UserIDEQ(userID), paymentorder.StatusIn(OrderStatusPaid, OrderStatusRecharging, OrderStatusCompleted), paymentorder.PaidAtGTE(ts)).All(ctx) + if err != nil { + return fmt.Errorf("query daily usage: %w", err) + } + var used float64 + for _, o := range orders { + used += o.Amount + } + if used+amount > limit { + return infraerrors.TooManyRequests("DAILY_LIMIT_EXCEEDED", fmt.Sprintf("daily recharge limit reached, remaining: %.2f", math.Max(0, limit-used))) + } + return nil +} + +func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.PaymentOrder, req CreateOrderRequest, cfg *PaymentConfig, payAmountStr string, payAmount float64, plan *dbent.SubscriptionPlan) (*CreateOrderResponse, error) { + s.EnsureProviders(ctx) + providerKey := s.registry.GetProviderKey(req.PaymentType) + if providerKey == "" { + return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment method (%s) is not configured", req.PaymentType)) + } + sel, err := s.loadBalancer.SelectInstance(ctx, providerKey, req.PaymentType, payment.Strategy(cfg.LoadBalanceStrategy), payAmount) + if err != nil { + return nil, fmt.Errorf("select provider instance: %w", err) + } + if sel == nil { + return nil, infraerrors.TooManyRequests("NO_AVAILABLE_INSTANCE", "no available payment instance") + } + prov, err := provider.CreateProvider(providerKey, sel.InstanceID, sel.Config) + if err != nil { + return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", "payment method is temporarily unavailable") + } + subject := s.buildPaymentSubject(plan, payAmountStr, cfg) + outTradeNo := order.OutTradeNo + pr, err := prov.CreatePayment(ctx, payment.CreatePaymentRequest{OrderID: outTradeNo, Amount: payAmountStr, PaymentType: req.PaymentType, Subject: subject, ClientIP: req.ClientIP, IsMobile: req.IsMobile, InstanceSubMethods: sel.SupportedTypes}) + if err != nil { + slog.Error("[PaymentService] CreatePayment failed", "provider", providerKey, "instance", sel.InstanceID, "error", err) + return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment gateway error: %s", err.Error())) + } + _, err = s.entClient.PaymentOrder.UpdateOneID(order.ID).SetNillablePaymentTradeNo(psNilIfEmpty(pr.TradeNo)).SetNillablePayURL(psNilIfEmpty(pr.PayURL)).SetNillableQrCode(psNilIfEmpty(pr.QRCode)).SetNillableProviderInstanceID(psNilIfEmpty(sel.InstanceID)).Save(ctx) + if err != nil { + return nil, fmt.Errorf("update order with payment details: %w", err) + } + s.writeAuditLog(ctx, order.ID, "ORDER_CREATED", fmt.Sprintf("user:%d", req.UserID), map[string]any{"amount": req.Amount, "paymentType": req.PaymentType, "orderType": req.OrderType}) + return &CreateOrderResponse{OrderID: order.ID, Amount: order.Amount, PayAmount: payAmount, FeeRate: order.FeeRate, Status: OrderStatusPending, PaymentType: req.PaymentType, PayURL: pr.PayURL, QRCode: pr.QRCode, ClientSecret: pr.ClientSecret, ExpiresAt: order.ExpiresAt, PaymentMode: sel.PaymentMode}, nil +} + +func (s *PaymentService) buildPaymentSubject(plan *dbent.SubscriptionPlan, payAmountStr string, cfg *PaymentConfig) string { + if plan != nil { + if plan.ProductName != "" { + return plan.ProductName + } + return "Sub2API Subscription " + plan.Name + } + pf := strings.TrimSpace(cfg.ProductNamePrefix) + sf := strings.TrimSpace(cfg.ProductNameSuffix) + if pf != "" || sf != "" { + return strings.TrimSpace(pf + " " + payAmountStr + " " + sf) + } + return "Sub2API " + payAmountStr + " CNY" +} + +// --- Order Queries --- + +func (s *PaymentService) GetOrder(ctx context.Context, orderID, userID int64) (*dbent.PaymentOrder, error) { + o, err := s.entClient.PaymentOrder.Get(ctx, orderID) + if err != nil { + return nil, infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.UserID != userID { + return nil, infraerrors.Forbidden("FORBIDDEN", "no permission for this order") + } + return o, nil +} + +func (s *PaymentService) GetOrderByID(ctx context.Context, orderID int64) (*dbent.PaymentOrder, error) { + o, err := s.entClient.PaymentOrder.Get(ctx, orderID) + if err != nil { + return nil, infraerrors.NotFound("NOT_FOUND", "order not found") + } + return o, nil +} + +func (s *PaymentService) GetUserOrders(ctx context.Context, userID int64, p OrderListParams) ([]*dbent.PaymentOrder, int, error) { + q := s.entClient.PaymentOrder.Query().Where(paymentorder.UserIDEQ(userID)) + if p.Status != "" { + q = q.Where(paymentorder.StatusEQ(p.Status)) + } + if p.OrderType != "" { + q = q.Where(paymentorder.OrderTypeEQ(p.OrderType)) + } + if p.PaymentType != "" { + q = q.Where(paymentorder.PaymentTypeEQ(p.PaymentType)) + } + total, err := q.Clone().Count(ctx) + if err != nil { + return nil, 0, fmt.Errorf("count user orders: %w", err) + } + ps, pg := applyPagination(p.PageSize, p.Page) + orders, err := q.Order(dbent.Desc(paymentorder.FieldCreatedAt)).Limit(ps).Offset((pg - 1) * ps).All(ctx) + if err != nil { + return nil, 0, fmt.Errorf("query user orders: %w", err) + } + return orders, total, nil +} + +// AdminListOrders returns a paginated list of orders. If userID > 0, filters by user. +func (s *PaymentService) AdminListOrders(ctx context.Context, userID int64, p OrderListParams) ([]*dbent.PaymentOrder, int, error) { + q := s.entClient.PaymentOrder.Query() + if userID > 0 { + q = q.Where(paymentorder.UserIDEQ(userID)) + } + if p.Status != "" { + q = q.Where(paymentorder.StatusEQ(p.Status)) + } + if p.OrderType != "" { + q = q.Where(paymentorder.OrderTypeEQ(p.OrderType)) + } + if p.PaymentType != "" { + q = q.Where(paymentorder.PaymentTypeEQ(p.PaymentType)) + } + if p.Keyword != "" { + q = q.Where(paymentorder.Or( + paymentorder.OutTradeNoContainsFold(p.Keyword), + paymentorder.UserEmailContainsFold(p.Keyword), + paymentorder.UserNameContainsFold(p.Keyword), + )) + } + total, err := q.Clone().Count(ctx) + if err != nil { + return nil, 0, fmt.Errorf("count admin orders: %w", err) + } + ps, pg := applyPagination(p.PageSize, p.Page) + orders, err := q.Order(dbent.Desc(paymentorder.FieldCreatedAt)).Limit(ps).Offset((pg - 1) * ps).All(ctx) + if err != nil { + return nil, 0, fmt.Errorf("query admin orders: %w", err) + } + return orders, total, nil +} diff --git a/backend/internal/service/payment_order_expiry_service.go b/backend/internal/service/payment_order_expiry_service.go new file mode 100644 index 0000000000000000000000000000000000000000..b0cda3e5914bc7fbf8c3ad73a70e420462a7264c --- /dev/null +++ b/backend/internal/service/payment_order_expiry_service.go @@ -0,0 +1,73 @@ +package service + +import ( + "context" + "log/slog" + "sync" + "time" +) + +const expiryCheckTimeout = 30 * time.Second + +// PaymentOrderExpiryService periodically expires timed-out payment orders. +type PaymentOrderExpiryService struct { + paymentSvc *PaymentService + interval time.Duration + stopCh chan struct{} + stopOnce sync.Once + wg sync.WaitGroup +} + +func NewPaymentOrderExpiryService(paymentSvc *PaymentService, interval time.Duration) *PaymentOrderExpiryService { + return &PaymentOrderExpiryService{ + paymentSvc: paymentSvc, + interval: interval, + stopCh: make(chan struct{}), + } +} + +func (s *PaymentOrderExpiryService) Start() { + if s == nil || s.paymentSvc == nil || s.interval <= 0 { + return + } + s.wg.Add(1) + go func() { + defer s.wg.Done() + ticker := time.NewTicker(s.interval) + defer ticker.Stop() + + s.runOnce() + for { + select { + case <-ticker.C: + s.runOnce() + case <-s.stopCh: + return + } + } + }() +} + +func (s *PaymentOrderExpiryService) Stop() { + if s == nil { + return + } + s.stopOnce.Do(func() { + close(s.stopCh) + }) + s.wg.Wait() +} + +func (s *PaymentOrderExpiryService) runOnce() { + ctx, cancel := context.WithTimeout(context.Background(), expiryCheckTimeout) + defer cancel() + + expired, err := s.paymentSvc.ExpireTimedOutOrders(ctx) + if err != nil { + slog.Error("[PaymentOrderExpiry] failed to expire orders", "error", err) + return + } + if expired > 0 { + slog.Info("[PaymentOrderExpiry] expired timed-out orders", "count", expired) + } +} diff --git a/backend/internal/service/payment_order_lifecycle.go b/backend/internal/service/payment_order_lifecycle.go new file mode 100644 index 0000000000000000000000000000000000000000..801471804c7597ea667ca86279b35177a3aec95f --- /dev/null +++ b/backend/internal/service/payment_order_lifecycle.go @@ -0,0 +1,257 @@ +package service + +import ( + "context" + "fmt" + "log/slog" + "strconv" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/Wei-Shaw/sub2api/internal/payment/provider" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// --- Cancel & Expire --- + +// Cancel rate limit configuration constants. +const ( + rateLimitUnitDay = "day" + rateLimitUnitMinute = "minute" + rateLimitUnitHour = "hour" + rateLimitModeFixed = "fixed" + checkPaidResultAlreadyPaid = "already_paid" + checkPaidResultCancelled = "cancelled" +) + +func (s *PaymentService) checkCancelRateLimit(ctx context.Context, userID int64, cfg *PaymentConfig) error { + if !cfg.CancelRateLimitEnabled || cfg.CancelRateLimitMax <= 0 { + return nil + } + windowStart := cancelRateLimitWindowStart(cfg) + operator := fmt.Sprintf("user:%d", userID) + count, err := s.entClient.PaymentAuditLog.Query(). + Where( + paymentauditlog.ActionEQ("ORDER_CANCELLED"), + paymentauditlog.OperatorEQ(operator), + paymentauditlog.CreatedAtGTE(windowStart), + ).Count(ctx) + if err != nil { + slog.Error("check cancel rate limit failed", "userID", userID, "error", err) + return nil // fail open + } + if count >= cfg.CancelRateLimitMax { + return infraerrors.TooManyRequests("CANCEL_RATE_LIMITED", "cancel rate limited"). + WithMetadata(map[string]string{ + "max": strconv.Itoa(cfg.CancelRateLimitMax), + "window": strconv.Itoa(cfg.CancelRateLimitWindow), + "unit": cfg.CancelRateLimitUnit, + }) + } + return nil +} + +func cancelRateLimitWindowStart(cfg *PaymentConfig) time.Time { + now := time.Now() + w := cfg.CancelRateLimitWindow + if w <= 0 { + w = 1 + } + unit := cfg.CancelRateLimitUnit + if unit == "" { + unit = rateLimitUnitDay + } + if cfg.CancelRateLimitMode == rateLimitModeFixed { + switch unit { + case rateLimitUnitMinute: + t := now.Truncate(time.Minute) + return t.Add(-time.Duration(w-1) * time.Minute) + case rateLimitUnitDay: + y, m, d := now.Date() + t := time.Date(y, m, d, 0, 0, 0, 0, now.Location()) + return t.AddDate(0, 0, -(w - 1)) + default: // hour + t := now.Truncate(time.Hour) + return t.Add(-time.Duration(w-1) * time.Hour) + } + } + // rolling window + switch unit { + case rateLimitUnitMinute: + return now.Add(-time.Duration(w) * time.Minute) + case rateLimitUnitDay: + return now.AddDate(0, 0, -w) + default: // hour + return now.Add(-time.Duration(w) * time.Hour) + } +} + +func (s *PaymentService) CancelOrder(ctx context.Context, orderID, userID int64) (string, error) { + o, err := s.entClient.PaymentOrder.Get(ctx, orderID) + if err != nil { + return "", infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.UserID != userID { + return "", infraerrors.Forbidden("FORBIDDEN", "no permission for this order") + } + if o.Status != OrderStatusPending { + return "", infraerrors.BadRequest("INVALID_STATUS", "order cannot be cancelled in current status") + } + return s.cancelCore(ctx, o, OrderStatusCancelled, fmt.Sprintf("user:%d", userID), "user cancelled order") +} + +func (s *PaymentService) AdminCancelOrder(ctx context.Context, orderID int64) (string, error) { + o, err := s.entClient.PaymentOrder.Get(ctx, orderID) + if err != nil { + return "", infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.Status != OrderStatusPending { + return "", infraerrors.BadRequest("INVALID_STATUS", "order cannot be cancelled in current status") + } + return s.cancelCore(ctx, o, OrderStatusCancelled, "admin", "admin cancelled order") +} + +func (s *PaymentService) cancelCore(ctx context.Context, o *dbent.PaymentOrder, fs, op, ad string) (string, error) { + if o.PaymentTradeNo != "" || o.PaymentType != "" { + if s.checkPaid(ctx, o) == checkPaidResultAlreadyPaid { + return checkPaidResultAlreadyPaid, nil + } + } + c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(o.ID), paymentorder.StatusEQ(OrderStatusPending)).SetStatus(fs).Save(ctx) + if err != nil { + return "", fmt.Errorf("update order status: %w", err) + } + if c > 0 { + auditAction := "ORDER_CANCELLED" + if fs == OrderStatusExpired { + auditAction = "ORDER_EXPIRED" + } + s.writeAuditLog(ctx, o.ID, auditAction, op, map[string]any{"detail": ad}) + } + return checkPaidResultCancelled, nil +} + +func (s *PaymentService) checkPaid(ctx context.Context, o *dbent.PaymentOrder) string { + prov, err := s.getOrderProvider(ctx, o) + if err != nil { + return "" + } + // Use OutTradeNo as fallback when PaymentTradeNo is empty + // (e.g. EasyPay popup mode where trade_no arrives only via notify callback) + tradeNo := o.PaymentTradeNo + if tradeNo == "" { + tradeNo = o.OutTradeNo + } + resp, err := prov.QueryOrder(ctx, tradeNo) + if err != nil { + slog.Warn("query upstream failed", "orderID", o.ID, "error", err) + return "" + } + if resp.Status == payment.ProviderStatusPaid { + if err := s.HandlePaymentNotification(ctx, &payment.PaymentNotification{TradeNo: o.PaymentTradeNo, OrderID: o.OutTradeNo, Amount: resp.Amount, Status: payment.ProviderStatusSuccess}, prov.ProviderKey()); err != nil { + slog.Error("fulfillment failed during checkPaid", "orderID", o.ID, "error", err) + // Still return already_paid — order was paid, fulfillment can be retried + } + return checkPaidResultAlreadyPaid + } + if cp, ok := prov.(payment.CancelableProvider); ok { + _ = cp.CancelPayment(ctx, tradeNo) + } + return "" +} + +// VerifyOrderByOutTradeNo actively queries the upstream provider to check +// if a payment was made, and processes it if so. This handles the case where +// the provider's notify callback was missed (e.g. EasyPay popup mode). +func (s *PaymentService) VerifyOrderByOutTradeNo(ctx context.Context, outTradeNo string, userID int64) (*dbent.PaymentOrder, error) { + o, err := s.entClient.PaymentOrder.Query(). + Where(paymentorder.OutTradeNo(outTradeNo)). + Only(ctx) + if err != nil { + return nil, infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.UserID != userID { + return nil, infraerrors.Forbidden("FORBIDDEN", "no permission for this order") + } + // Only verify orders that are still pending or recently expired + if o.Status == OrderStatusPending || o.Status == OrderStatusExpired { + result := s.checkPaid(ctx, o) + if result == checkPaidResultAlreadyPaid { + // Reload order to get updated status + o, err = s.entClient.PaymentOrder.Get(ctx, o.ID) + if err != nil { + return nil, fmt.Errorf("reload order: %w", err) + } + } + } + return o, nil +} + +// VerifyOrderPublic verifies payment status without user authentication. +// Used by the payment result page when the user's session has expired. +func (s *PaymentService) VerifyOrderPublic(ctx context.Context, outTradeNo string) (*dbent.PaymentOrder, error) { + o, err := s.entClient.PaymentOrder.Query(). + Where(paymentorder.OutTradeNo(outTradeNo)). + Only(ctx) + if err != nil { + return nil, infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.Status == OrderStatusPending || o.Status == OrderStatusExpired { + result := s.checkPaid(ctx, o) + if result == checkPaidResultAlreadyPaid { + o, err = s.entClient.PaymentOrder.Get(ctx, o.ID) + if err != nil { + return nil, fmt.Errorf("reload order: %w", err) + } + } + } + return o, nil +} + +func (s *PaymentService) ExpireTimedOutOrders(ctx context.Context) (int, error) { + now := time.Now() + orders, err := s.entClient.PaymentOrder.Query().Where(paymentorder.StatusEQ(OrderStatusPending), paymentorder.ExpiresAtLTE(now)).All(ctx) + if err != nil { + return 0, fmt.Errorf("query expired: %w", err) + } + n := 0 + for _, o := range orders { + // Check upstream payment status before expiring — the user may have + // paid just before timeout and the webhook hasn't arrived yet. + outcome, _ := s.cancelCore(ctx, o, OrderStatusExpired, "system", "order expired") + if outcome == checkPaidResultAlreadyPaid { + slog.Info("order was paid during expiry", "orderID", o.ID) + continue + } + if outcome != "" { + n++ + } + } + return n, nil +} + +// getOrderProvider creates a provider using the order's original instance config. +// Falls back to registry lookup if instance ID is missing (legacy orders). +func (s *PaymentService) getOrderProvider(ctx context.Context, o *dbent.PaymentOrder) (payment.Provider, error) { + if o.ProviderInstanceID != nil && *o.ProviderInstanceID != "" { + instID, err := strconv.ParseInt(*o.ProviderInstanceID, 10, 64) + if err == nil { + cfg, err := s.loadBalancer.GetInstanceConfig(ctx, instID) + if err == nil { + providerKey := s.registry.GetProviderKey(o.PaymentType) + if providerKey == "" { + providerKey = o.PaymentType + } + p, err := provider.CreateProvider(providerKey, *o.ProviderInstanceID, cfg) + if err == nil { + return p, nil + } + } + } + } + s.EnsureProviders(ctx) + return s.registry.GetProvider(o.PaymentType) +} diff --git a/backend/internal/service/payment_refund.go b/backend/internal/service/payment_refund.go new file mode 100644 index 0000000000000000000000000000000000000000..fd2822cc41f86aedb80ae36e51d89e5bd38d31fc --- /dev/null +++ b/backend/internal/service/payment_refund.go @@ -0,0 +1,248 @@ +package service + +import ( + "context" + "fmt" + "log/slog" + "math" + "strconv" + "strings" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/internal/payment" + infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" +) + +// --- Refund Flow --- + +func (s *PaymentService) RequestRefund(ctx context.Context, oid, uid int64, reason string) error { + o, err := s.validateRefundRequest(ctx, oid, uid) + if err != nil { + return err + } + u, err := s.userRepo.GetByID(ctx, o.UserID) + if err != nil { + return fmt.Errorf("get user: %w", err) + } + if u.Balance < o.Amount { + return infraerrors.BadRequest("BALANCE_NOT_ENOUGH", "refund amount exceeds balance") + } + nr := strings.TrimSpace(reason) + now := time.Now() + by := fmt.Sprintf("%d", uid) + c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.UserIDEQ(uid), paymentorder.StatusEQ(OrderStatusCompleted), paymentorder.OrderTypeEQ(payment.OrderTypeBalance)).SetStatus(OrderStatusRefundRequested).SetRefundRequestedAt(now).SetRefundRequestReason(nr).SetRefundRequestedBy(by).SetRefundAmount(o.Amount).Save(ctx) + if err != nil { + return fmt.Errorf("update: %w", err) + } + if c == 0 { + return infraerrors.Conflict("CONFLICT", "order status changed") + } + s.writeAuditLog(ctx, oid, "REFUND_REQUESTED", fmt.Sprintf("user:%d", uid), map[string]any{"amount": o.Amount, "reason": nr}) + return nil +} + +func (s *PaymentService) validateRefundRequest(ctx context.Context, oid, uid int64) (*dbent.PaymentOrder, error) { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + return nil, infraerrors.NotFound("NOT_FOUND", "order not found") + } + if o.UserID != uid { + return nil, infraerrors.Forbidden("FORBIDDEN", "no permission") + } + if o.OrderType != payment.OrderTypeBalance { + return nil, infraerrors.BadRequest("INVALID_ORDER_TYPE", "only balance orders can request refund") + } + if o.Status != OrderStatusCompleted { + return nil, infraerrors.BadRequest("INVALID_STATUS", "only completed orders can request refund") + } + return o, nil +} + +func (s *PaymentService) PrepareRefund(ctx context.Context, oid int64, amt float64, reason string, force, deduct bool) (*RefundPlan, *RefundResult, error) { + o, err := s.entClient.PaymentOrder.Get(ctx, oid) + if err != nil { + return nil, nil, infraerrors.NotFound("NOT_FOUND", "order not found") + } + ok := []string{OrderStatusCompleted, OrderStatusRefundRequested, OrderStatusRefundFailed} + if !psSliceContains(ok, o.Status) { + return nil, nil, infraerrors.BadRequest("INVALID_STATUS", "order status does not allow refund") + } + if math.IsNaN(amt) || math.IsInf(amt, 0) { + return nil, nil, infraerrors.BadRequest("INVALID_AMOUNT", "invalid refund amount") + } + if amt <= 0 { + amt = o.Amount + } + if amt-o.Amount > amountToleranceCNY { + return nil, nil, infraerrors.BadRequest("REFUND_AMOUNT_EXCEEDED", "refund amount exceeds recharge") + } + // Full refund: use actual pay_amount for gateway (includes fees) + ga := amt + if math.Abs(amt-o.Amount) <= amountToleranceCNY { + ga = o.PayAmount + } + rr := strings.TrimSpace(reason) + if rr == "" && o.RefundRequestReason != nil { + rr = *o.RefundRequestReason + } + if rr == "" { + rr = fmt.Sprintf("refund order:%d", o.ID) + } + p := &RefundPlan{OrderID: oid, Order: o, RefundAmount: amt, GatewayAmount: ga, Reason: rr, Force: force, DeductBalance: deduct, DeductionType: payment.DeductionTypeNone} + if deduct { + if er := s.prepDeduct(ctx, o, p, force); er != nil { + return nil, er, nil + } + } + return p, nil, nil +} + +func (s *PaymentService) prepDeduct(ctx context.Context, o *dbent.PaymentOrder, p *RefundPlan, force bool) *RefundResult { + if o.OrderType == payment.OrderTypeSubscription { + p.DeductionType = payment.DeductionTypeSubscription + if o.SubscriptionGroupID != nil && o.SubscriptionDays != nil { + p.SubDaysToDeduct = *o.SubscriptionDays + sub, err := s.subscriptionSvc.GetActiveSubscription(ctx, o.UserID, *o.SubscriptionGroupID) + if err == nil && sub != nil { + p.SubscriptionID = sub.ID + } else if !force { + return &RefundResult{Success: false, Warning: "cannot find active subscription for deduction, use force", RequireForce: true} + } + } + return nil + } + u, err := s.userRepo.GetByID(ctx, o.UserID) + if err != nil { + if !force { + return &RefundResult{Success: false, Warning: "cannot fetch user balance, use force", RequireForce: true} + } + return nil + } + p.DeductionType = payment.DeductionTypeBalance + p.BalanceToDeduct = math.Min(p.RefundAmount, u.Balance) + return nil +} + +func (s *PaymentService) ExecuteRefund(ctx context.Context, p *RefundPlan) (*RefundResult, error) { + c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(p.OrderID), paymentorder.StatusIn(OrderStatusCompleted, OrderStatusRefundRequested, OrderStatusRefundFailed)).SetStatus(OrderStatusRefunding).Save(ctx) + if err != nil { + return nil, fmt.Errorf("lock: %w", err) + } + if c == 0 { + return nil, infraerrors.Conflict("CONFLICT", "order status changed") + } + if p.DeductionType == payment.DeductionTypeBalance && p.BalanceToDeduct > 0 { + // Skip balance deduction on retry if previous attempt already deducted + // but failed to roll back (REFUND_ROLLBACK_FAILED in audit log). + if !s.hasAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED") { + if err := s.userRepo.DeductBalance(ctx, p.Order.UserID, p.BalanceToDeduct); err != nil { + s.restoreStatus(ctx, p) + return nil, fmt.Errorf("deduction: %w", err) + } + } else { + slog.Warn("skipping balance deduction on retry (previous rollback failed)", "orderID", p.OrderID) + p.BalanceToDeduct = 0 + } + } + if p.DeductionType == payment.DeductionTypeSubscription && p.SubDaysToDeduct > 0 && p.SubscriptionID > 0 { + if !s.hasAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED") { + _, err := s.subscriptionSvc.ExtendSubscription(ctx, p.SubscriptionID, -p.SubDaysToDeduct) + if err != nil { + // If deducting would expire the subscription, revoke it entirely + slog.Info("subscription deduction would expire, revoking", "orderID", p.OrderID, "subID", p.SubscriptionID, "days", p.SubDaysToDeduct) + if revokeErr := s.subscriptionSvc.RevokeSubscription(ctx, p.SubscriptionID); revokeErr != nil { + s.restoreStatus(ctx, p) + return nil, fmt.Errorf("revoke subscription: %w", revokeErr) + } + } + } else { + slog.Warn("skipping subscription deduction on retry (previous rollback failed)", "orderID", p.OrderID) + p.SubDaysToDeduct = 0 + } + } + if err := s.gwRefund(ctx, p); err != nil { + return s.handleGwFail(ctx, p, err) + } + return s.markRefundOk(ctx, p) +} + +func (s *PaymentService) gwRefund(ctx context.Context, p *RefundPlan) error { + if p.Order.PaymentTradeNo == "" { + s.writeAuditLog(ctx, p.Order.ID, "REFUND_NO_TRADE_NO", "admin", map[string]any{"detail": "skipped"}) + return nil + } + + // Use the exact provider instance that created this order, not a random one + // from the registry. Each instance has its own merchant credentials. + prov, err := s.getRefundProvider(ctx, p.Order) + if err != nil { + return fmt.Errorf("get refund provider: %w", err) + } + _, err = prov.Refund(ctx, payment.RefundRequest{ + TradeNo: p.Order.PaymentTradeNo, + OrderID: p.Order.OutTradeNo, + Amount: strconv.FormatFloat(p.GatewayAmount, 'f', 2, 64), + Reason: p.Reason, + }) + return err +} + +// getRefundProvider creates a provider using the order's original instance config. +// Delegates to getOrderProvider which handles instance lookup and fallback. +func (s *PaymentService) getRefundProvider(ctx context.Context, o *dbent.PaymentOrder) (payment.Provider, error) { + return s.getOrderProvider(ctx, o) +} + +func (s *PaymentService) handleGwFail(ctx context.Context, p *RefundPlan, gErr error) (*RefundResult, error) { + if s.RollbackRefund(ctx, p, gErr) { + s.restoreStatus(ctx, p) + s.writeAuditLog(ctx, p.OrderID, "REFUND_GATEWAY_FAILED", "admin", map[string]any{"detail": psErrMsg(gErr)}) + return &RefundResult{Success: false, Warning: "gateway failed: " + psErrMsg(gErr) + ", rolled back"}, nil + } + now := time.Now() + _, _ = s.entClient.PaymentOrder.UpdateOneID(p.OrderID).SetStatus(OrderStatusRefundFailed).SetFailedAt(now).SetFailedReason(psErrMsg(gErr)).Save(ctx) + s.writeAuditLog(ctx, p.OrderID, "REFUND_FAILED", "admin", map[string]any{"detail": psErrMsg(gErr)}) + return nil, infraerrors.InternalServer("REFUND_FAILED", psErrMsg(gErr)) +} + +func (s *PaymentService) markRefundOk(ctx context.Context, p *RefundPlan) (*RefundResult, error) { + fs := OrderStatusRefunded + if p.RefundAmount < p.Order.Amount { + fs = OrderStatusPartiallyRefunded + } + now := time.Now() + _, err := s.entClient.PaymentOrder.UpdateOneID(p.OrderID).SetStatus(fs).SetRefundAmount(p.RefundAmount).SetRefundReason(p.Reason).SetRefundAt(now).SetForceRefund(p.Force).Save(ctx) + if err != nil { + return nil, fmt.Errorf("mark refund: %w", err) + } + s.writeAuditLog(ctx, p.OrderID, "REFUND_SUCCESS", "admin", map[string]any{"refundAmount": p.RefundAmount, "reason": p.Reason, "balanceDeducted": p.BalanceToDeduct, "force": p.Force}) + return &RefundResult{Success: true, BalanceDeducted: p.BalanceToDeduct, SubDaysDeducted: p.SubDaysToDeduct}, nil +} + +func (s *PaymentService) RollbackRefund(ctx context.Context, p *RefundPlan, gErr error) bool { + if p.DeductionType == payment.DeductionTypeBalance && p.BalanceToDeduct > 0 { + if err := s.userRepo.UpdateBalance(ctx, p.Order.UserID, p.BalanceToDeduct); err != nil { + slog.Error("[CRITICAL] rollback failed", "orderID", p.OrderID, "amount", p.BalanceToDeduct, "error", err) + s.writeAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED", "admin", map[string]any{"gatewayError": psErrMsg(gErr), "rollbackError": psErrMsg(err), "balanceDeducted": p.BalanceToDeduct}) + return false + } + } + if p.DeductionType == payment.DeductionTypeSubscription && p.SubDaysToDeduct > 0 && p.SubscriptionID > 0 { + if _, err := s.subscriptionSvc.ExtendSubscription(ctx, p.SubscriptionID, p.SubDaysToDeduct); err != nil { + slog.Error("[CRITICAL] subscription rollback failed", "orderID", p.OrderID, "subID", p.SubscriptionID, "days", p.SubDaysToDeduct, "error", err) + s.writeAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED", "admin", map[string]any{"gatewayError": psErrMsg(gErr), "rollbackError": psErrMsg(err), "subDaysDeducted": p.SubDaysToDeduct}) + return false + } + } + return true +} + +func (s *PaymentService) restoreStatus(ctx context.Context, p *RefundPlan) { + rs := OrderStatusCompleted + if p.Order.Status == OrderStatusRefundRequested { + rs = OrderStatusRefundRequested + } + _, _ = s.entClient.PaymentOrder.UpdateOneID(p.OrderID).SetStatus(rs).Save(ctx) +} diff --git a/backend/internal/service/payment_service.go b/backend/internal/service/payment_service.go new file mode 100644 index 0000000000000000000000000000000000000000..6d8b185e976fc89b02efd2782ed59d57fde59960 --- /dev/null +++ b/backend/internal/service/payment_service.go @@ -0,0 +1,311 @@ +package service + +import ( + "context" + "fmt" + "log/slog" + "math/rand/v2" + "sync" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" + "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance" + "github.com/Wei-Shaw/sub2api/internal/payment" + "github.com/Wei-Shaw/sub2api/internal/payment/provider" +) + +// --- Order Status Constants --- + +const ( + OrderStatusPending = payment.OrderStatusPending + OrderStatusPaid = payment.OrderStatusPaid + OrderStatusRecharging = payment.OrderStatusRecharging + OrderStatusCompleted = payment.OrderStatusCompleted + OrderStatusExpired = payment.OrderStatusExpired + OrderStatusCancelled = payment.OrderStatusCancelled + OrderStatusFailed = payment.OrderStatusFailed + OrderStatusRefundRequested = payment.OrderStatusRefundRequested + OrderStatusRefunding = payment.OrderStatusRefunding + OrderStatusPartiallyRefunded = payment.OrderStatusPartiallyRefunded + OrderStatusRefunded = payment.OrderStatusRefunded + OrderStatusRefundFailed = payment.OrderStatusRefundFailed +) + +const ( + // defaultMaxPendingOrders and defaultOrderTimeoutMin are defined in + // payment_config_service.go alongside other payment configuration defaults. + paymentGraceMinutes = 5 + + defaultPageSize = 20 + maxPageSize = 100 + topUsersLimit = 10 + amountToleranceCNY = 0.01 + + orderIDPrefix = "sub2_" +) + +// --- Types --- + +// generateOutTradeNo creates a unique external order ID for payment providers. +// Format: sub2_20250409aB3kX9mQ (prefix + date + 8-char random) +func generateOutTradeNo() string { + date := time.Now().Format("20060102") + rnd := generateRandomString(8) + return orderIDPrefix + date + rnd +} + +func generateRandomString(n int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, n) + for i := range b { + b[i] = charset[rand.IntN(len(charset))] + } + return string(b) +} + +type CreateOrderRequest struct { + UserID int64 + Amount float64 + PaymentType string + ClientIP string + IsMobile bool + SrcHost string + SrcURL string + OrderType string + PlanID int64 +} + +type CreateOrderResponse struct { + OrderID int64 `json:"order_id"` + Amount float64 `json:"amount"` + PayAmount float64 `json:"pay_amount"` + FeeRate float64 `json:"fee_rate"` + Status string `json:"status"` + PaymentType string `json:"payment_type"` + PayURL string `json:"pay_url,omitempty"` + QRCode string `json:"qr_code,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + ExpiresAt time.Time `json:"expires_at"` + PaymentMode string `json:"payment_mode,omitempty"` +} + +type OrderListParams struct { + Page int + PageSize int + Status string + OrderType string + PaymentType string + Keyword string +} + +type RefundPlan struct { + OrderID int64 + Order *dbent.PaymentOrder + RefundAmount float64 + GatewayAmount float64 + Reason string + Force bool + DeductBalance bool + DeductionType string + BalanceToDeduct float64 + SubDaysToDeduct int + SubscriptionID int64 +} + +type RefundResult struct { + Success bool `json:"success"` + Warning string `json:"warning,omitempty"` + RequireForce bool `json:"require_force,omitempty"` + BalanceDeducted float64 `json:"balance_deducted,omitempty"` + SubDaysDeducted int `json:"subscription_days_deducted,omitempty"` +} + +type DashboardStats struct { + TodayAmount float64 `json:"today_amount"` + TotalAmount float64 `json:"total_amount"` + TodayCount int `json:"today_count"` + TotalCount int `json:"total_count"` + AvgAmount float64 `json:"avg_amount"` + PendingOrders int `json:"pending_orders"` + + DailySeries []DailyStats `json:"daily_series"` + PaymentMethods []PaymentMethodStat `json:"payment_methods"` + TopUsers []TopUserStat `json:"top_users"` +} + +type DailyStats struct { + Date string `json:"date"` + Amount float64 `json:"amount"` + Count int `json:"count"` +} + +type PaymentMethodStat struct { + Type string `json:"type"` + Amount float64 `json:"amount"` + Count int `json:"count"` +} + +type TopUserStat struct { + UserID int64 `json:"user_id"` + Email string `json:"email"` + Amount float64 `json:"amount"` +} + +// --- Service --- + +type PaymentService struct { + providerMu sync.Mutex + providersLoaded bool + entClient *dbent.Client + registry *payment.Registry + loadBalancer payment.LoadBalancer + redeemService *RedeemService + subscriptionSvc *SubscriptionService + configService *PaymentConfigService + userRepo UserRepository + groupRepo GroupRepository +} + +func NewPaymentService(entClient *dbent.Client, registry *payment.Registry, loadBalancer payment.LoadBalancer, redeemService *RedeemService, subscriptionSvc *SubscriptionService, configService *PaymentConfigService, userRepo UserRepository, groupRepo GroupRepository) *PaymentService { + return &PaymentService{entClient: entClient, registry: registry, loadBalancer: loadBalancer, redeemService: redeemService, subscriptionSvc: subscriptionSvc, configService: configService, userRepo: userRepo, groupRepo: groupRepo} +} + +// --- Provider Registry --- + +// EnsureProviders lazily initializes the provider registry on first call. +func (s *PaymentService) EnsureProviders(ctx context.Context) { + s.providerMu.Lock() + defer s.providerMu.Unlock() + if !s.providersLoaded { + s.loadProviders(ctx) + s.providersLoaded = true + } +} + +// RefreshProviders clears and re-registers all providers from the database. +func (s *PaymentService) RefreshProviders(ctx context.Context) { + s.providerMu.Lock() + defer s.providerMu.Unlock() + s.registry.Clear() + s.loadProviders(ctx) + s.providersLoaded = true +} + +func (s *PaymentService) loadProviders(ctx context.Context) { + instances, err := s.entClient.PaymentProviderInstance.Query(). + Where(paymentproviderinstance.EnabledEQ(true)). + All(ctx) + if err != nil { + slog.Error("[PaymentService] failed to query provider instances", "error", err) + return + } + for _, inst := range instances { + cfg, err := s.loadBalancer.GetInstanceConfig(ctx, int64(inst.ID)) + if err != nil { + slog.Warn("[PaymentService] failed to decrypt config for instance", "instanceID", inst.ID, "error", err) + continue + } + if inst.PaymentMode != "" { + cfg["paymentMode"] = inst.PaymentMode + } + instID := fmt.Sprintf("%d", inst.ID) + p, err := provider.CreateProvider(inst.ProviderKey, instID, cfg) + if err != nil { + slog.Warn("[PaymentService] failed to create provider for instance", "instanceID", inst.ID, "key", inst.ProviderKey, "error", err) + continue + } + s.registry.Register(p) + } +} + +// GetWebhookProvider returns the provider instance that should verify a webhook. +// It extracts out_trade_no from the raw body, looks up the order to find the +// original provider instance, and creates a provider with that instance's credentials. +// Falls back to the registry provider when the order cannot be found. +func (s *PaymentService) GetWebhookProvider(ctx context.Context, providerKey, outTradeNo string) (payment.Provider, error) { + if outTradeNo != "" { + order, err := s.entClient.PaymentOrder.Query().Where(paymentorder.OutTradeNo(outTradeNo)).Only(ctx) + if err == nil { + p, pErr := s.getOrderProvider(ctx, order) + if pErr == nil { + return p, nil + } + slog.Warn("[Webhook] order provider creation failed, falling back to registry", "outTradeNo", outTradeNo, "error", pErr) + } + } + s.EnsureProviders(ctx) + return s.registry.GetProviderByKey(providerKey) +} + +// --- Helpers --- + +func psIsRefundStatus(s string) bool { + switch s { + case OrderStatusRefundRequested, OrderStatusRefunding, OrderStatusPartiallyRefunded, OrderStatusRefunded, OrderStatusRefundFailed: + return true + } + return false +} + +func psErrMsg(err error) string { + if err == nil { + return "" + } + return err.Error() +} + +func psNilIfEmpty(s string) *string { + if s == "" { + return nil + } + return &s +} + +func psSliceContains(sl []string, s string) bool { + for _, v := range sl { + if v == s { + return true + } + } + return false +} + +// Subscription validity period unit constants. +const ( + validityUnitWeek = "week" + validityUnitMonth = "month" +) + +func psComputeValidityDays(days int, unit string) int { + switch unit { + case validityUnitWeek: + return days * 7 + case validityUnitMonth: + return days * 30 + default: + return days + } +} + +func (s *PaymentService) getFeeRate(_ string) float64 { return 0 } + +func psStartOfDayUTC(t time.Time) time.Time { + y, m, d := t.UTC().Date() + return time.Date(y, m, d, 0, 0, 0, 0, time.UTC) +} + +func applyPagination(pageSize, page int) (size, pg int) { + size = pageSize + if size <= 0 { + size = defaultPageSize + } + if size > maxPageSize { + size = maxPageSize + } + pg = page + if pg < 1 { + pg = 1 + } + return size, pg +} diff --git a/backend/internal/service/payment_stats.go b/backend/internal/service/payment_stats.go new file mode 100644 index 0000000000000000000000000000000000000000..d206b271717135e58b970a19fa032b4c0c4c00ba --- /dev/null +++ b/backend/internal/service/payment_stats.go @@ -0,0 +1,163 @@ +package service + +import ( + "context" + "encoding/json" + "log/slog" + "math" + "sort" + "strconv" + "time" + + dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/ent/paymentauditlog" + "github.com/Wei-Shaw/sub2api/ent/paymentorder" +) + +// --- Dashboard & Analytics --- + +func (s *PaymentService) GetDashboardStats(ctx context.Context, days int) (*DashboardStats, error) { + if days <= 0 { + days = 30 + } + now := time.Now() + since := now.AddDate(0, 0, -days) + todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + + paidStatuses := []string{OrderStatusCompleted, OrderStatusPaid, OrderStatusRecharging} + + orders, err := s.entClient.PaymentOrder.Query(). + Where( + paymentorder.StatusIn(paidStatuses...), + paymentorder.PaidAtGTE(since), + ). + All(ctx) + if err != nil { + return nil, err + } + + st := &DashboardStats{} + computeBasicStats(st, orders, todayStart) + + st.PendingOrders, err = s.entClient.PaymentOrder.Query(). + Where(paymentorder.StatusEQ(OrderStatusPending)). + Count(ctx) + if err != nil { + return nil, err + } + + st.DailySeries = buildDailySeries(orders, since, days) + st.PaymentMethods = buildMethodDistribution(orders) + st.TopUsers = buildTopUsers(orders) + + return st, nil +} + +func computeBasicStats(st *DashboardStats, orders []*dbent.PaymentOrder, todayStart time.Time) { + var totalAmount, todayAmount float64 + var todayCount int + for _, o := range orders { + totalAmount += o.PayAmount + if o.PaidAt != nil && !o.PaidAt.Before(todayStart) { + todayAmount += o.PayAmount + todayCount++ + } + } + st.TotalAmount = math.Round(totalAmount*100) / 100 + st.TodayAmount = math.Round(todayAmount*100) / 100 + st.TotalCount = len(orders) + st.TodayCount = todayCount + if st.TotalCount > 0 { + st.AvgAmount = math.Round(totalAmount/float64(st.TotalCount)*100) / 100 + } +} + +func buildDailySeries(orders []*dbent.PaymentOrder, since time.Time, days int) []DailyStats { + dailyMap := make(map[string]*DailyStats) + for _, o := range orders { + if o.PaidAt == nil { + continue + } + date := o.PaidAt.Format("2006-01-02") + ds, ok := dailyMap[date] + if !ok { + ds = &DailyStats{Date: date} + dailyMap[date] = ds + } + ds.Amount += o.PayAmount + ds.Count++ + } + series := make([]DailyStats, 0, days) + for i := 0; i < days; i++ { + date := since.AddDate(0, 0, i+1).Format("2006-01-02") + if ds, ok := dailyMap[date]; ok { + ds.Amount = math.Round(ds.Amount*100) / 100 + series = append(series, *ds) + } else { + series = append(series, DailyStats{Date: date}) + } + } + return series +} + +func buildMethodDistribution(orders []*dbent.PaymentOrder) []PaymentMethodStat { + methodMap := make(map[string]*PaymentMethodStat) + for _, o := range orders { + ms, ok := methodMap[o.PaymentType] + if !ok { + ms = &PaymentMethodStat{Type: o.PaymentType} + methodMap[o.PaymentType] = ms + } + ms.Amount += o.PayAmount + ms.Count++ + } + methods := make([]PaymentMethodStat, 0, len(methodMap)) + for _, ms := range methodMap { + ms.Amount = math.Round(ms.Amount*100) / 100 + methods = append(methods, *ms) + } + return methods +} + +func buildTopUsers(orders []*dbent.PaymentOrder) []TopUserStat { + userMap := make(map[int64]*TopUserStat) + for _, o := range orders { + us, ok := userMap[o.UserID] + if !ok { + us = &TopUserStat{UserID: o.UserID, Email: o.UserEmail} + userMap[o.UserID] = us + } + us.Amount += o.PayAmount + } + userList := make([]*TopUserStat, 0, len(userMap)) + for _, us := range userMap { + us.Amount = math.Round(us.Amount*100) / 100 + userList = append(userList, us) + } + sort.Slice(userList, func(i, j int) bool { + return userList[i].Amount > userList[j].Amount + }) + limit := topUsersLimit + if len(userList) < limit { + limit = len(userList) + } + result := make([]TopUserStat, 0, limit) + for i := 0; i < limit; i++ { + result = append(result, *userList[i]) + } + return result +} + +// --- Audit Logs --- + +func (s *PaymentService) writeAuditLog(ctx context.Context, oid int64, action, op string, detail map[string]any) { + dj, _ := json.Marshal(detail) + _, err := s.entClient.PaymentAuditLog.Create().SetOrderID(strconv.FormatInt(oid, 10)).SetAction(action).SetDetail(string(dj)).SetOperator(op).Save(ctx) + if err != nil { + slog.Error("audit log failed", "orderID", oid, "action", action, "error", err) + } +} + +func (s *PaymentService) GetOrderAuditLogs(ctx context.Context, oid int64) ([]*dbent.PaymentAuditLog, error) { + return s.entClient.PaymentAuditLog.Query().Where(paymentauditlog.OrderIDEQ(strconv.FormatInt(oid, 10))).Order(paymentauditlog.ByCreatedAt()).All(ctx) +} diff --git a/backend/internal/service/ratelimit_service.go b/backend/internal/service/ratelimit_service.go index 4f5b57cc978137646debe14cdca449e7647c9cef..4d8009b7bd39471da48550367d3eb18c656d4219 100644 --- a/backend/internal/service/ratelimit_service.go +++ b/backend/internal/service/ratelimit_service.go @@ -142,11 +142,16 @@ func (s *RateLimitService) HandleUpstreamError(ctx context.Context, account *Acc switch statusCode { case 400: - // 只有当错误信息包含 "organization has been disabled" 时才禁用 + // "organization has been disabled" → 永久禁用 if strings.Contains(strings.ToLower(upstreamMsg), "organization has been disabled") { msg := "Organization disabled (400): " + upstreamMsg s.handleAuthError(ctx, account, msg) shouldDisable = true + } else if account.Platform == PlatformAnthropic && strings.Contains(strings.ToLower(upstreamMsg), "credit balance") { + // Anthropic API key 余额不足(语义等同 402),停止调度 + msg := "Credit balance exhausted (400): " + upstreamMsg + s.handleAuthError(ctx, account, msg) + shouldDisable = true } // 其他 400 错误(如参数问题)不处理,不禁用账号 case 401: diff --git a/backend/internal/service/scheduler_snapshot_hydration_test.go b/backend/internal/service/scheduler_snapshot_hydration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5c0b289b7a4d1714456555a585d6f4d7e6a82d8d --- /dev/null +++ b/backend/internal/service/scheduler_snapshot_hydration_test.go @@ -0,0 +1,159 @@ +//go:build unit + +package service + +import ( + "context" + "testing" + "time" +) + +type snapshotHydrationCache struct { + snapshot []*Account + accounts map[int64]*Account +} + +func (c *snapshotHydrationCache) GetSnapshot(ctx context.Context, bucket SchedulerBucket) ([]*Account, bool, error) { + return c.snapshot, true, nil +} + +func (c *snapshotHydrationCache) SetSnapshot(ctx context.Context, bucket SchedulerBucket, accounts []Account) error { + return nil +} + +func (c *snapshotHydrationCache) GetAccount(ctx context.Context, accountID int64) (*Account, error) { + if c.accounts == nil { + return nil, nil + } + return c.accounts[accountID], nil +} + +func (c *snapshotHydrationCache) SetAccount(ctx context.Context, account *Account) error { + return nil +} + +func (c *snapshotHydrationCache) DeleteAccount(ctx context.Context, accountID int64) error { + return nil +} + +func (c *snapshotHydrationCache) UpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error { + return nil +} + +func (c *snapshotHydrationCache) TryLockBucket(ctx context.Context, bucket SchedulerBucket, ttl time.Duration) (bool, error) { + return true, nil +} + +func (c *snapshotHydrationCache) ListBuckets(ctx context.Context) ([]SchedulerBucket, error) { + return nil, nil +} + +func (c *snapshotHydrationCache) GetOutboxWatermark(ctx context.Context) (int64, error) { + return 0, nil +} + +func (c *snapshotHydrationCache) SetOutboxWatermark(ctx context.Context, id int64) error { + return nil +} + +func TestOpenAISelectAccountWithLoadAwareness_HydratesSelectedAccountFromSchedulerSnapshot(t *testing.T) { + cache := &snapshotHydrationCache{ + snapshot: []*Account{ + { + ID: 1, + Platform: PlatformOpenAI, + Type: AccountTypeAPIKey, + Status: StatusActive, + Schedulable: true, + Concurrency: 1, + Priority: 1, + Credentials: map[string]any{ + "model_mapping": map[string]any{ + "gpt-4": "gpt-4", + }, + }, + }, + }, + accounts: map[int64]*Account{ + 1: { + ID: 1, + Platform: PlatformOpenAI, + Type: AccountTypeAPIKey, + Status: StatusActive, + Schedulable: true, + Concurrency: 1, + Priority: 1, + Credentials: map[string]any{ + "api_key": "sk-live", + "model_mapping": map[string]any{"gpt-4": "gpt-4"}, + }, + }, + }, + } + + schedulerSnapshot := NewSchedulerSnapshotService(cache, nil, nil, nil, nil) + groupID := int64(2) + svc := &OpenAIGatewayService{ + schedulerSnapshot: schedulerSnapshot, + cache: &stubGatewayCache{}, + } + + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + if err != nil { + t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) + } + if selection == nil || selection.Account == nil { + t.Fatalf("expected selected account") + } + if got := selection.Account.GetOpenAIApiKey(); got != "sk-live" { + t.Fatalf("expected hydrated api key, got %q", got) + } +} + +func TestGatewaySelectAccountWithLoadAwareness_HydratesSelectedAccountFromSchedulerSnapshot(t *testing.T) { + cache := &snapshotHydrationCache{ + snapshot: []*Account{ + { + ID: 9, + Platform: PlatformAnthropic, + Type: AccountTypeAPIKey, + Status: StatusActive, + Schedulable: true, + Concurrency: 1, + Priority: 1, + }, + }, + accounts: map[int64]*Account{ + 9: { + ID: 9, + Platform: PlatformAnthropic, + Type: AccountTypeAPIKey, + Status: StatusActive, + Schedulable: true, + Concurrency: 1, + Priority: 1, + Credentials: map[string]any{ + "api_key": "anthropic-live-key", + }, + }, + }, + } + + schedulerSnapshot := NewSchedulerSnapshotService(cache, nil, nil, nil, nil) + svc := &GatewayService{ + schedulerSnapshot: schedulerSnapshot, + cache: &mockGatewayCacheForPlatform{}, + cfg: testConfig(), + } + + result, err := svc.SelectAccountWithLoadAwareness(context.Background(), nil, "", "claude-3-5-sonnet-20241022", nil, "", 0) + if err != nil { + t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) + } + if result == nil || result.Account == nil { + t.Fatalf("expected selected account") + } + if got := result.Account.GetCredential("api_key"); got != "anthropic-live-key" { + t.Fatalf("expected hydrated api key, got %q", got) + } +} diff --git a/backend/internal/service/setting_service.go b/backend/internal/service/setting_service.go index 60a697227860625b5a75899dcf5bdf679d11f76e..f00ff640394fd5aacebe3ee44fec98b8238700ab 100644 --- a/backend/internal/service/setting_service.go +++ b/backend/internal/service/setting_service.go @@ -9,6 +9,7 @@ import ( "fmt" "log/slog" "net/url" + "sort" "strconv" "strings" "sync/atomic" @@ -16,6 +17,7 @@ import ( "github.com/Wei-Shaw/sub2api/internal/config" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" + "github.com/imroc/req/v3" "golang.org/x/sync/singleflight" ) @@ -160,10 +162,15 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings SettingKeyHideCcsImportButton, SettingKeyPurchaseSubscriptionEnabled, SettingKeyPurchaseSubscriptionURL, + SettingKeyTableDefaultPageSize, + SettingKeyTablePageSizeOptions, SettingKeyCustomMenuItems, SettingKeyCustomEndpoints, SettingKeyLinuxDoConnectEnabled, SettingKeyBackendModeEnabled, + SettingKeyOIDCConnectEnabled, + SettingKeyOIDCConnectProviderName, + SettingPaymentEnabled, } settings, err := s.settingRepo.GetMultiple(ctx, keys) @@ -177,6 +184,19 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings } else { linuxDoEnabled = s.cfg != nil && s.cfg.LinuxDo.Enabled } + oidcEnabled := false + if raw, ok := settings[SettingKeyOIDCConnectEnabled]; ok { + oidcEnabled = raw == "true" + } else { + oidcEnabled = s.cfg != nil && s.cfg.OIDC.Enabled + } + oidcProviderName := strings.TrimSpace(settings[SettingKeyOIDCConnectProviderName]) + if oidcProviderName == "" && s.cfg != nil { + oidcProviderName = strings.TrimSpace(s.cfg.OIDC.ProviderName) + } + if oidcProviderName == "" { + oidcProviderName = "OIDC" + } // Password reset requires email verification to be enabled emailVerifyEnabled := settings[SettingKeyEmailVerifyEnabled] == "true" @@ -184,6 +204,10 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings registrationEmailSuffixWhitelist := ParseRegistrationEmailSuffixWhitelist( settings[SettingKeyRegistrationEmailSuffixWhitelist], ) + tableDefaultPageSize, tablePageSizeOptions := parseTablePreferences( + settings[SettingKeyTableDefaultPageSize], + settings[SettingKeyTablePageSizeOptions], + ) return &PublicSettings{ RegistrationEnabled: settings[SettingKeyRegistrationEnabled] == "true", @@ -205,10 +229,15 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings HideCcsImportButton: settings[SettingKeyHideCcsImportButton] == "true", PurchaseSubscriptionEnabled: settings[SettingKeyPurchaseSubscriptionEnabled] == "true", PurchaseSubscriptionURL: strings.TrimSpace(settings[SettingKeyPurchaseSubscriptionURL]), + TableDefaultPageSize: tableDefaultPageSize, + TablePageSizeOptions: tablePageSizeOptions, CustomMenuItems: settings[SettingKeyCustomMenuItems], CustomEndpoints: settings[SettingKeyCustomEndpoints], LinuxDoOAuthEnabled: linuxDoEnabled, BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true", + OIDCOAuthEnabled: oidcEnabled, + OIDCOAuthProviderName: oidcProviderName, + PaymentEnabled: settings[SettingPaymentEnabled] == "true", }, nil } @@ -252,10 +281,15 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any HideCcsImportButton bool `json:"hide_ccs_import_button"` PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"` PurchaseSubscriptionURL string `json:"purchase_subscription_url,omitempty"` + TableDefaultPageSize int `json:"table_default_page_size"` + TablePageSizeOptions []int `json:"table_page_size_options"` CustomMenuItems json.RawMessage `json:"custom_menu_items"` CustomEndpoints json.RawMessage `json:"custom_endpoints"` LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"` BackendModeEnabled bool `json:"backend_mode_enabled"` + OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"` + OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"` + PaymentEnabled bool `json:"payment_enabled"` Version string `json:"version,omitempty"` }{ RegistrationEnabled: settings.RegistrationEnabled, @@ -277,10 +311,15 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any HideCcsImportButton: settings.HideCcsImportButton, PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled, PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL, + TableDefaultPageSize: settings.TableDefaultPageSize, + TablePageSizeOptions: settings.TablePageSizeOptions, CustomMenuItems: filterUserVisibleMenuItems(settings.CustomMenuItems), CustomEndpoints: safeRawJSONArray(settings.CustomEndpoints), LinuxDoOAuthEnabled: settings.LinuxDoOAuthEnabled, BackendModeEnabled: settings.BackendModeEnabled, + OIDCOAuthEnabled: settings.OIDCOAuthEnabled, + OIDCOAuthProviderName: settings.OIDCOAuthProviderName, + PaymentEnabled: settings.PaymentEnabled, Version: s.version, }, nil } @@ -333,8 +372,8 @@ func safeRawJSONArray(raw string) json.RawMessage { return json.RawMessage("[]") } -// GetFrameSrcOrigins returns deduplicated http(s) origins from purchase_subscription_url -// and all custom_menu_items URLs. Used by the router layer for CSP frame-src injection. +// GetFrameSrcOrigins returns deduplicated http(s) origins from home_content URL, +// purchase_subscription_url, and all custom_menu_items URLs. Used by the router layer for CSP frame-src injection. func (s *SettingService) GetFrameSrcOrigins(ctx context.Context) ([]string, error) { settings, err := s.GetPublicSettings(ctx) if err != nil { @@ -353,6 +392,9 @@ func (s *SettingService) GetFrameSrcOrigins(ctx context.Context) ([]string, erro } } + // home content URL (when home_content is set to a URL for iframe embedding) + addOrigin(settings.HomeContent) + // purchase subscription URL if settings.PurchaseSubscriptionEnabled { addOrigin(settings.PurchaseSubscriptionURL) @@ -460,6 +502,32 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet updates[SettingKeyLinuxDoConnectClientSecret] = settings.LinuxDoConnectClientSecret } + // Generic OIDC OAuth 登录 + updates[SettingKeyOIDCConnectEnabled] = strconv.FormatBool(settings.OIDCConnectEnabled) + updates[SettingKeyOIDCConnectProviderName] = settings.OIDCConnectProviderName + updates[SettingKeyOIDCConnectClientID] = settings.OIDCConnectClientID + updates[SettingKeyOIDCConnectIssuerURL] = settings.OIDCConnectIssuerURL + updates[SettingKeyOIDCConnectDiscoveryURL] = settings.OIDCConnectDiscoveryURL + updates[SettingKeyOIDCConnectAuthorizeURL] = settings.OIDCConnectAuthorizeURL + updates[SettingKeyOIDCConnectTokenURL] = settings.OIDCConnectTokenURL + updates[SettingKeyOIDCConnectUserInfoURL] = settings.OIDCConnectUserInfoURL + updates[SettingKeyOIDCConnectJWKSURL] = settings.OIDCConnectJWKSURL + updates[SettingKeyOIDCConnectScopes] = settings.OIDCConnectScopes + updates[SettingKeyOIDCConnectRedirectURL] = settings.OIDCConnectRedirectURL + updates[SettingKeyOIDCConnectFrontendRedirectURL] = settings.OIDCConnectFrontendRedirectURL + updates[SettingKeyOIDCConnectTokenAuthMethod] = settings.OIDCConnectTokenAuthMethod + updates[SettingKeyOIDCConnectUsePKCE] = strconv.FormatBool(settings.OIDCConnectUsePKCE) + updates[SettingKeyOIDCConnectValidateIDToken] = strconv.FormatBool(settings.OIDCConnectValidateIDToken) + updates[SettingKeyOIDCConnectAllowedSigningAlgs] = settings.OIDCConnectAllowedSigningAlgs + updates[SettingKeyOIDCConnectClockSkewSeconds] = strconv.Itoa(settings.OIDCConnectClockSkewSeconds) + updates[SettingKeyOIDCConnectRequireEmailVerified] = strconv.FormatBool(settings.OIDCConnectRequireEmailVerified) + updates[SettingKeyOIDCConnectUserInfoEmailPath] = settings.OIDCConnectUserInfoEmailPath + updates[SettingKeyOIDCConnectUserInfoIDPath] = settings.OIDCConnectUserInfoIDPath + updates[SettingKeyOIDCConnectUserInfoUsernamePath] = settings.OIDCConnectUserInfoUsernamePath + if settings.OIDCConnectClientSecret != "" { + updates[SettingKeyOIDCConnectClientSecret] = settings.OIDCConnectClientSecret + } + // OEM设置 updates[SettingKeySiteName] = settings.SiteName updates[SettingKeySiteLogo] = settings.SiteLogo @@ -471,6 +539,16 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet updates[SettingKeyHideCcsImportButton] = strconv.FormatBool(settings.HideCcsImportButton) updates[SettingKeyPurchaseSubscriptionEnabled] = strconv.FormatBool(settings.PurchaseSubscriptionEnabled) updates[SettingKeyPurchaseSubscriptionURL] = strings.TrimSpace(settings.PurchaseSubscriptionURL) + tableDefaultPageSize, tablePageSizeOptions := normalizeTablePreferences( + settings.TableDefaultPageSize, + settings.TablePageSizeOptions, + ) + updates[SettingKeyTableDefaultPageSize] = strconv.Itoa(tableDefaultPageSize) + tablePageSizeOptionsJSON, err := json.Marshal(tablePageSizeOptions) + if err != nil { + return fmt.Errorf("marshal table page size options: %w", err) + } + updates[SettingKeyTablePageSizeOptions] = string(tablePageSizeOptionsJSON) updates[SettingKeyCustomMenuItems] = settings.CustomMenuItems updates[SettingKeyCustomEndpoints] = settings.CustomEndpoints @@ -824,8 +902,12 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { SettingKeySiteLogo: "", SettingKeyPurchaseSubscriptionEnabled: "false", SettingKeyPurchaseSubscriptionURL: "", + SettingKeyTableDefaultPageSize: "20", + SettingKeyTablePageSizeOptions: "[10,20,50,100]", SettingKeyCustomMenuItems: "[]", SettingKeyCustomEndpoints: "[]", + SettingKeyOIDCConnectEnabled: "false", + SettingKeyOIDCConnectProviderName: "OIDC", SettingKeyDefaultConcurrency: strconv.Itoa(s.cfg.Default.UserConcurrency), SettingKeyDefaultBalance: strconv.FormatFloat(s.cfg.Default.UserBalance, 'f', 8, 64), SettingKeyDefaultSubscriptions: "[]", @@ -893,6 +975,10 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin CustomEndpoints: settings[SettingKeyCustomEndpoints], BackendModeEnabled: settings[SettingKeyBackendModeEnabled] == "true", } + result.TableDefaultPageSize, result.TablePageSizeOptions = parseTablePreferences( + settings[SettingKeyTableDefaultPageSize], + settings[SettingKeyTablePageSizeOptions], + ) // 解析整数类型 if port, err := strconv.Atoi(settings[SettingKeySMTPPort]); err == nil { @@ -951,6 +1037,138 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin } result.LinuxDoConnectClientSecretConfigured = result.LinuxDoConnectClientSecret != "" + // Generic OIDC 设置: + // - 兼容 config.yaml/env + // - 支持后台系统设置覆盖并持久化(存储于 DB) + oidcBase := config.OIDCConnectConfig{} + if s.cfg != nil { + oidcBase = s.cfg.OIDC + } + + if raw, ok := settings[SettingKeyOIDCConnectEnabled]; ok { + result.OIDCConnectEnabled = raw == "true" + } else { + result.OIDCConnectEnabled = oidcBase.Enabled + } + + if v, ok := settings[SettingKeyOIDCConnectProviderName]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectProviderName = strings.TrimSpace(v) + } else { + result.OIDCConnectProviderName = strings.TrimSpace(oidcBase.ProviderName) + } + if result.OIDCConnectProviderName == "" { + result.OIDCConnectProviderName = "OIDC" + } + + if v, ok := settings[SettingKeyOIDCConnectClientID]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectClientID = strings.TrimSpace(v) + } else { + result.OIDCConnectClientID = strings.TrimSpace(oidcBase.ClientID) + } + if v, ok := settings[SettingKeyOIDCConnectIssuerURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectIssuerURL = strings.TrimSpace(v) + } else { + result.OIDCConnectIssuerURL = strings.TrimSpace(oidcBase.IssuerURL) + } + if v, ok := settings[SettingKeyOIDCConnectDiscoveryURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectDiscoveryURL = strings.TrimSpace(v) + } else { + result.OIDCConnectDiscoveryURL = strings.TrimSpace(oidcBase.DiscoveryURL) + } + if v, ok := settings[SettingKeyOIDCConnectAuthorizeURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectAuthorizeURL = strings.TrimSpace(v) + } else { + result.OIDCConnectAuthorizeURL = strings.TrimSpace(oidcBase.AuthorizeURL) + } + if v, ok := settings[SettingKeyOIDCConnectTokenURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectTokenURL = strings.TrimSpace(v) + } else { + result.OIDCConnectTokenURL = strings.TrimSpace(oidcBase.TokenURL) + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectUserInfoURL = strings.TrimSpace(v) + } else { + result.OIDCConnectUserInfoURL = strings.TrimSpace(oidcBase.UserInfoURL) + } + if v, ok := settings[SettingKeyOIDCConnectJWKSURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectJWKSURL = strings.TrimSpace(v) + } else { + result.OIDCConnectJWKSURL = strings.TrimSpace(oidcBase.JWKSURL) + } + if v, ok := settings[SettingKeyOIDCConnectScopes]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectScopes = strings.TrimSpace(v) + } else { + result.OIDCConnectScopes = strings.TrimSpace(oidcBase.Scopes) + } + if v, ok := settings[SettingKeyOIDCConnectRedirectURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectRedirectURL = strings.TrimSpace(v) + } else { + result.OIDCConnectRedirectURL = strings.TrimSpace(oidcBase.RedirectURL) + } + if v, ok := settings[SettingKeyOIDCConnectFrontendRedirectURL]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectFrontendRedirectURL = strings.TrimSpace(v) + } else { + result.OIDCConnectFrontendRedirectURL = strings.TrimSpace(oidcBase.FrontendRedirectURL) + } + if v, ok := settings[SettingKeyOIDCConnectTokenAuthMethod]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectTokenAuthMethod = strings.ToLower(strings.TrimSpace(v)) + } else { + result.OIDCConnectTokenAuthMethod = strings.ToLower(strings.TrimSpace(oidcBase.TokenAuthMethod)) + } + if raw, ok := settings[SettingKeyOIDCConnectUsePKCE]; ok { + result.OIDCConnectUsePKCE = raw == "true" + } else { + result.OIDCConnectUsePKCE = oidcBase.UsePKCE + } + if raw, ok := settings[SettingKeyOIDCConnectValidateIDToken]; ok { + result.OIDCConnectValidateIDToken = raw == "true" + } else { + result.OIDCConnectValidateIDToken = oidcBase.ValidateIDToken + } + if v, ok := settings[SettingKeyOIDCConnectAllowedSigningAlgs]; ok && strings.TrimSpace(v) != "" { + result.OIDCConnectAllowedSigningAlgs = strings.TrimSpace(v) + } else { + result.OIDCConnectAllowedSigningAlgs = strings.TrimSpace(oidcBase.AllowedSigningAlgs) + } + clockSkewSet := false + if raw, ok := settings[SettingKeyOIDCConnectClockSkewSeconds]; ok && strings.TrimSpace(raw) != "" { + if parsed, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil { + result.OIDCConnectClockSkewSeconds = parsed + clockSkewSet = true + } + } + if !clockSkewSet { + result.OIDCConnectClockSkewSeconds = oidcBase.ClockSkewSeconds + } + if !clockSkewSet && result.OIDCConnectClockSkewSeconds == 0 { + result.OIDCConnectClockSkewSeconds = 120 + } + if raw, ok := settings[SettingKeyOIDCConnectRequireEmailVerified]; ok { + result.OIDCConnectRequireEmailVerified = raw == "true" + } else { + result.OIDCConnectRequireEmailVerified = oidcBase.RequireEmailVerified + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoEmailPath]; ok { + result.OIDCConnectUserInfoEmailPath = strings.TrimSpace(v) + } else { + result.OIDCConnectUserInfoEmailPath = strings.TrimSpace(oidcBase.UserInfoEmailPath) + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoIDPath]; ok { + result.OIDCConnectUserInfoIDPath = strings.TrimSpace(v) + } else { + result.OIDCConnectUserInfoIDPath = strings.TrimSpace(oidcBase.UserInfoIDPath) + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoUsernamePath]; ok { + result.OIDCConnectUserInfoUsernamePath = strings.TrimSpace(v) + } else { + result.OIDCConnectUserInfoUsernamePath = strings.TrimSpace(oidcBase.UserInfoUsernamePath) + } + result.OIDCConnectClientSecret = strings.TrimSpace(settings[SettingKeyOIDCConnectClientSecret]) + if result.OIDCConnectClientSecret == "" { + result.OIDCConnectClientSecret = strings.TrimSpace(oidcBase.ClientSecret) + } + result.OIDCConnectClientSecretConfigured = result.OIDCConnectClientSecret != "" + // Model fallback settings result.EnableModelFallback = settings[SettingKeyEnableModelFallback] == "true" result.FallbackModelAnthropic = s.getStringOrDefault(settings, SettingKeyFallbackModelAnthropic, "claude-3-5-sonnet-20241022") @@ -1036,6 +1254,50 @@ func parseDefaultSubscriptions(raw string) []DefaultSubscriptionSetting { return normalized } +func parseTablePreferences(defaultPageSizeRaw, optionsRaw string) (int, []int) { + defaultPageSize := 20 + if v, err := strconv.Atoi(strings.TrimSpace(defaultPageSizeRaw)); err == nil { + defaultPageSize = v + } + + var options []int + if strings.TrimSpace(optionsRaw) != "" { + _ = json.Unmarshal([]byte(optionsRaw), &options) + } + + return normalizeTablePreferences(defaultPageSize, options) +} + +func normalizeTablePreferences(defaultPageSize int, options []int) (int, []int) { + const minPageSize = 5 + const maxPageSize = 1000 + const fallbackPageSize = 20 + + seen := make(map[int]struct{}, len(options)) + normalizedOptions := make([]int, 0, len(options)) + for _, option := range options { + if option < minPageSize || option > maxPageSize { + continue + } + if _, ok := seen[option]; ok { + continue + } + seen[option] = struct{}{} + normalizedOptions = append(normalizedOptions, option) + } + sort.Ints(normalizedOptions) + + if defaultPageSize < minPageSize || defaultPageSize > maxPageSize { + defaultPageSize = fallbackPageSize + } + + if len(normalizedOptions) == 0 { + normalizedOptions = []int{10, 20, 50} + } + + return defaultPageSize, normalizedOptions +} + // getStringOrDefault 获取字符串值或默认值 func (s *SettingService) getStringOrDefault(settings map[string]string, key, defaultValue string) string { if value, ok := settings[key]; ok && value != "" { @@ -1323,6 +1585,282 @@ func (s *SettingService) SetOverloadCooldownSettings(ctx context.Context, settin return s.settingRepo.Set(ctx, SettingKeyOverloadCooldownSettings, string(data)) } +// GetOIDCConnectOAuthConfig 返回用于登录的“最终生效” OIDC 配置。 +// +// 优先级: +// - 若对应系统设置键存在,则覆盖 config.yaml/env 的值 +// - 否则回退到 config.yaml/env 的值 +func (s *SettingService) GetOIDCConnectOAuthConfig(ctx context.Context) (config.OIDCConnectConfig, error) { + if s == nil || s.cfg == nil { + return config.OIDCConnectConfig{}, infraerrors.ServiceUnavailable("CONFIG_NOT_READY", "config not loaded") + } + + effective := s.cfg.OIDC + + keys := []string{ + SettingKeyOIDCConnectEnabled, + SettingKeyOIDCConnectProviderName, + SettingKeyOIDCConnectClientID, + SettingKeyOIDCConnectClientSecret, + SettingKeyOIDCConnectIssuerURL, + SettingKeyOIDCConnectDiscoveryURL, + SettingKeyOIDCConnectAuthorizeURL, + SettingKeyOIDCConnectTokenURL, + SettingKeyOIDCConnectUserInfoURL, + SettingKeyOIDCConnectJWKSURL, + SettingKeyOIDCConnectScopes, + SettingKeyOIDCConnectRedirectURL, + SettingKeyOIDCConnectFrontendRedirectURL, + SettingKeyOIDCConnectTokenAuthMethod, + SettingKeyOIDCConnectUsePKCE, + SettingKeyOIDCConnectValidateIDToken, + SettingKeyOIDCConnectAllowedSigningAlgs, + SettingKeyOIDCConnectClockSkewSeconds, + SettingKeyOIDCConnectRequireEmailVerified, + SettingKeyOIDCConnectUserInfoEmailPath, + SettingKeyOIDCConnectUserInfoIDPath, + SettingKeyOIDCConnectUserInfoUsernamePath, + } + settings, err := s.settingRepo.GetMultiple(ctx, keys) + if err != nil { + return config.OIDCConnectConfig{}, fmt.Errorf("get oidc connect settings: %w", err) + } + + if raw, ok := settings[SettingKeyOIDCConnectEnabled]; ok { + effective.Enabled = raw == "true" + } + if v, ok := settings[SettingKeyOIDCConnectProviderName]; ok && strings.TrimSpace(v) != "" { + effective.ProviderName = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectClientID]; ok && strings.TrimSpace(v) != "" { + effective.ClientID = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectClientSecret]; ok && strings.TrimSpace(v) != "" { + effective.ClientSecret = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectIssuerURL]; ok && strings.TrimSpace(v) != "" { + effective.IssuerURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectDiscoveryURL]; ok && strings.TrimSpace(v) != "" { + effective.DiscoveryURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectAuthorizeURL]; ok && strings.TrimSpace(v) != "" { + effective.AuthorizeURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectTokenURL]; ok && strings.TrimSpace(v) != "" { + effective.TokenURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoURL]; ok && strings.TrimSpace(v) != "" { + effective.UserInfoURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectJWKSURL]; ok && strings.TrimSpace(v) != "" { + effective.JWKSURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectScopes]; ok && strings.TrimSpace(v) != "" { + effective.Scopes = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectRedirectURL]; ok && strings.TrimSpace(v) != "" { + effective.RedirectURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectFrontendRedirectURL]; ok && strings.TrimSpace(v) != "" { + effective.FrontendRedirectURL = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectTokenAuthMethod]; ok && strings.TrimSpace(v) != "" { + effective.TokenAuthMethod = strings.ToLower(strings.TrimSpace(v)) + } + if raw, ok := settings[SettingKeyOIDCConnectUsePKCE]; ok { + effective.UsePKCE = raw == "true" + } + if raw, ok := settings[SettingKeyOIDCConnectValidateIDToken]; ok { + effective.ValidateIDToken = raw == "true" + } + if v, ok := settings[SettingKeyOIDCConnectAllowedSigningAlgs]; ok && strings.TrimSpace(v) != "" { + effective.AllowedSigningAlgs = strings.TrimSpace(v) + } + if raw, ok := settings[SettingKeyOIDCConnectClockSkewSeconds]; ok && strings.TrimSpace(raw) != "" { + if parsed, parseErr := strconv.Atoi(strings.TrimSpace(raw)); parseErr == nil { + effective.ClockSkewSeconds = parsed + } + } + if raw, ok := settings[SettingKeyOIDCConnectRequireEmailVerified]; ok { + effective.RequireEmailVerified = raw == "true" + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoEmailPath]; ok { + effective.UserInfoEmailPath = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoIDPath]; ok { + effective.UserInfoIDPath = strings.TrimSpace(v) + } + if v, ok := settings[SettingKeyOIDCConnectUserInfoUsernamePath]; ok { + effective.UserInfoUsernamePath = strings.TrimSpace(v) + } + + if !effective.Enabled { + return config.OIDCConnectConfig{}, infraerrors.NotFound("OAUTH_DISABLED", "oauth login is disabled") + } + if strings.TrimSpace(effective.ProviderName) == "" { + effective.ProviderName = "OIDC" + } + if strings.TrimSpace(effective.ClientID) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth client id not configured") + } + if strings.TrimSpace(effective.IssuerURL) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth issuer url not configured") + } + if strings.TrimSpace(effective.RedirectURL) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth redirect url not configured") + } + if strings.TrimSpace(effective.FrontendRedirectURL) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth frontend redirect url not configured") + } + if !scopesContainOpenID(effective.Scopes) { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth scopes must contain openid") + } + if effective.ClockSkewSeconds < 0 || effective.ClockSkewSeconds > 600 { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth clock skew must be between 0 and 600") + } + + if err := config.ValidateAbsoluteHTTPURL(effective.IssuerURL); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth issuer url invalid") + } + + discoveryURL := strings.TrimSpace(effective.DiscoveryURL) + if discoveryURL == "" { + discoveryURL = oidcDefaultDiscoveryURL(effective.IssuerURL) + effective.DiscoveryURL = discoveryURL + } + if discoveryURL != "" { + if err := config.ValidateAbsoluteHTTPURL(discoveryURL); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth discovery url invalid") + } + } + + needsDiscovery := strings.TrimSpace(effective.AuthorizeURL) == "" || + strings.TrimSpace(effective.TokenURL) == "" || + (effective.ValidateIDToken && strings.TrimSpace(effective.JWKSURL) == "") + if needsDiscovery && discoveryURL != "" { + metadata, resolveErr := oidcResolveProviderMetadata(ctx, discoveryURL) + if resolveErr != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth discovery resolve failed").WithCause(resolveErr) + } + if strings.TrimSpace(effective.AuthorizeURL) == "" { + effective.AuthorizeURL = strings.TrimSpace(metadata.AuthorizationEndpoint) + } + if strings.TrimSpace(effective.TokenURL) == "" { + effective.TokenURL = strings.TrimSpace(metadata.TokenEndpoint) + } + if strings.TrimSpace(effective.UserInfoURL) == "" { + effective.UserInfoURL = strings.TrimSpace(metadata.UserInfoEndpoint) + } + if strings.TrimSpace(effective.JWKSURL) == "" { + effective.JWKSURL = strings.TrimSpace(metadata.JWKSURI) + } + } + + if strings.TrimSpace(effective.AuthorizeURL) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth authorize url not configured") + } + if strings.TrimSpace(effective.TokenURL) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth token url not configured") + } + if err := config.ValidateAbsoluteHTTPURL(effective.AuthorizeURL); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth authorize url invalid") + } + if err := config.ValidateAbsoluteHTTPURL(effective.TokenURL); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth token url invalid") + } + if v := strings.TrimSpace(effective.UserInfoURL); v != "" { + if err := config.ValidateAbsoluteHTTPURL(v); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth userinfo url invalid") + } + } + if effective.ValidateIDToken { + if strings.TrimSpace(effective.JWKSURL) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth jwks url not configured") + } + if strings.TrimSpace(effective.AllowedSigningAlgs) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth signing algs not configured") + } + } + if v := strings.TrimSpace(effective.JWKSURL); v != "" { + if err := config.ValidateAbsoluteHTTPURL(v); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth jwks url invalid") + } + } + if err := config.ValidateAbsoluteHTTPURL(effective.RedirectURL); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth redirect url invalid") + } + if err := config.ValidateFrontendRedirectURL(effective.FrontendRedirectURL); err != nil { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth frontend redirect url invalid") + } + + method := strings.ToLower(strings.TrimSpace(effective.TokenAuthMethod)) + switch method { + case "", "client_secret_post", "client_secret_basic": + if strings.TrimSpace(effective.ClientSecret) == "" { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth client secret not configured") + } + case "none": + if !effective.UsePKCE { + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth pkce must be enabled when token_auth_method=none") + } + default: + return config.OIDCConnectConfig{}, infraerrors.InternalServer("OAUTH_CONFIG_INVALID", "oauth token_auth_method invalid") + } + + return effective, nil +} + +func scopesContainOpenID(scopes string) bool { + for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) { + if scope == "openid" { + return true + } + } + return false +} + +type oidcProviderMetadata struct { + AuthorizationEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + UserInfoEndpoint string `json:"userinfo_endpoint"` + JWKSURI string `json:"jwks_uri"` +} + +func oidcDefaultDiscoveryURL(issuerURL string) string { + issuerURL = strings.TrimSpace(issuerURL) + if issuerURL == "" { + return "" + } + return strings.TrimRight(issuerURL, "/") + "/.well-known/openid-configuration" +} + +func oidcResolveProviderMetadata(ctx context.Context, discoveryURL string) (*oidcProviderMetadata, error) { + discoveryURL = strings.TrimSpace(discoveryURL) + if discoveryURL == "" { + return nil, fmt.Errorf("discovery url is empty") + } + + resp, err := req.C(). + SetTimeout(15*time.Second). + R(). + SetContext(ctx). + SetHeader("Accept", "application/json"). + Get(discoveryURL) + if err != nil { + return nil, fmt.Errorf("request discovery document: %w", err) + } + if !resp.IsSuccessState() { + return nil, fmt.Errorf("discovery request failed: status=%d", resp.StatusCode) + } + + metadata := &oidcProviderMetadata{} + if err := json.Unmarshal(resp.Bytes(), metadata); err != nil { + return nil, fmt.Errorf("parse discovery document: %w", err) + } + return metadata, nil +} + // GetStreamTimeoutSettings 获取流超时处理配置 func (s *SettingService) GetStreamTimeoutSettings(ctx context.Context) (*StreamTimeoutSettings, error) { value, err := s.settingRepo.GetValue(ctx, SettingKeyStreamTimeoutSettings) diff --git a/backend/internal/service/setting_service_oidc_config_test.go b/backend/internal/service/setting_service_oidc_config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3809b332bd107e2c6559a9b657014a8f6db80b77 --- /dev/null +++ b/backend/internal/service/setting_service_oidc_config_test.go @@ -0,0 +1,103 @@ +//go:build unit + +package service + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/stretchr/testify/require" +) + +type settingOIDCRepoStub struct { + values map[string]string +} + +func (s *settingOIDCRepoStub) Get(ctx context.Context, key string) (*Setting, error) { + panic("unexpected Get call") +} + +func (s *settingOIDCRepoStub) GetValue(ctx context.Context, key string) (string, error) { + panic("unexpected GetValue call") +} + +func (s *settingOIDCRepoStub) Set(ctx context.Context, key, value string) error { + panic("unexpected Set call") +} + +func (s *settingOIDCRepoStub) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) { + out := make(map[string]string, len(keys)) + for _, key := range keys { + if value, ok := s.values[key]; ok { + out[key] = value + } + } + return out, nil +} + +func (s *settingOIDCRepoStub) SetMultiple(ctx context.Context, settings map[string]string) error { + panic("unexpected SetMultiple call") +} + +func (s *settingOIDCRepoStub) GetAll(ctx context.Context) (map[string]string, error) { + panic("unexpected GetAll call") +} + +func (s *settingOIDCRepoStub) Delete(ctx context.Context, key string) error { + panic("unexpected Delete call") +} + +func TestGetOIDCConnectOAuthConfig_ResolvesEndpointsFromIssuerDiscovery(t *testing.T) { + var discoveryHits int + var baseURL string + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/issuer/.well-known/openid-configuration" { + http.NotFound(w, r) + return + } + discoveryHits++ + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(fmt.Sprintf(`{ + "authorization_endpoint":"%s/issuer/protocol/openid-connect/auth", + "token_endpoint":"%s/issuer/protocol/openid-connect/token", + "userinfo_endpoint":"%s/issuer/protocol/openid-connect/userinfo", + "jwks_uri":"%s/issuer/protocol/openid-connect/certs" + }`, baseURL, baseURL, baseURL, baseURL))) + })) + defer srv.Close() + baseURL = srv.URL + + cfg := &config.Config{ + OIDC: config.OIDCConnectConfig{ + Enabled: true, + ProviderName: "OIDC", + ClientID: "oidc-client", + ClientSecret: "oidc-secret", + IssuerURL: srv.URL + "/issuer", + RedirectURL: "https://example.com/api/v1/auth/oauth/oidc/callback", + FrontendRedirectURL: "/auth/oidc/callback", + Scopes: "openid email profile", + TokenAuthMethod: "client_secret_post", + ValidateIDToken: true, + AllowedSigningAlgs: "RS256", + ClockSkewSeconds: 120, + }, + } + + repo := &settingOIDCRepoStub{values: map[string]string{}} + svc := NewSettingService(repo, cfg) + + got, err := svc.GetOIDCConnectOAuthConfig(context.Background()) + require.NoError(t, err) + require.Equal(t, 1, discoveryHits) + require.Equal(t, srv.URL+"/issuer/.well-known/openid-configuration", got.DiscoveryURL) + require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/auth", got.AuthorizeURL) + require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/token", got.TokenURL) + require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/userinfo", got.UserInfoURL) + require.Equal(t, srv.URL+"/issuer/protocol/openid-connect/certs", got.JWKSURL) +} diff --git a/backend/internal/service/setting_service_public_test.go b/backend/internal/service/setting_service_public_test.go index b511cd29dcf86e979ac143d53cf2d55f5da83122..6dfa627cfa4c41a60acdd2b1260a280dde6c6f27 100644 --- a/backend/internal/service/setting_service_public_test.go +++ b/backend/internal/service/setting_service_public_test.go @@ -62,3 +62,18 @@ func TestSettingService_GetPublicSettings_ExposesRegistrationEmailSuffixWhitelis require.NoError(t, err) require.Equal(t, []string{"@example.com", "@foo.bar"}, settings.RegistrationEmailSuffixWhitelist) } + +func TestSettingService_GetPublicSettings_ExposesTablePreferences(t *testing.T) { + repo := &settingPublicRepoStub{ + values: map[string]string{ + SettingKeyTableDefaultPageSize: "50", + SettingKeyTablePageSizeOptions: "[20,50,100]", + }, + } + svc := NewSettingService(repo, &config.Config{}) + + settings, err := svc.GetPublicSettings(context.Background()) + require.NoError(t, err) + require.Equal(t, 50, settings.TableDefaultPageSize) + require.Equal(t, []int{20, 50, 100}, settings.TablePageSizeOptions) +} diff --git a/backend/internal/service/setting_service_update_test.go b/backend/internal/service/setting_service_update_test.go index 1de08611e22c466ab9da065219237a9e3c9f31ae..28c7ad022096a506756a5c396199ea273615e2de 100644 --- a/backend/internal/service/setting_service_update_test.go +++ b/backend/internal/service/setting_service_update_test.go @@ -202,3 +202,24 @@ func TestParseDefaultSubscriptions_NormalizesValues(t *testing.T) { {GroupID: 12, ValidityDays: MaxValidityDays}, }, got) } + +func TestSettingService_UpdateSettings_TablePreferences(t *testing.T) { + repo := &settingUpdateRepoStub{} + svc := NewSettingService(repo, &config.Config{}) + + err := svc.UpdateSettings(context.Background(), &SystemSettings{ + TableDefaultPageSize: 50, + TablePageSizeOptions: []int{20, 50, 100}, + }) + require.NoError(t, err) + require.Equal(t, "50", repo.updates[SettingKeyTableDefaultPageSize]) + require.Equal(t, "[20,50,100]", repo.updates[SettingKeyTablePageSizeOptions]) + + err = svc.UpdateSettings(context.Background(), &SystemSettings{ + TableDefaultPageSize: 1000, + TablePageSizeOptions: []int{20, 100}, + }) + require.NoError(t, err) + require.Equal(t, "1000", repo.updates[SettingKeyTableDefaultPageSize]) + require.Equal(t, "[20,100]", repo.updates[SettingKeyTablePageSizeOptions]) +} diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index fedb3f2fe1cb772cd49632835294d7dd7e5bb606..de92b796dc92c0d545fadb6285db03329a403151 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -31,6 +31,31 @@ type SystemSettings struct { LinuxDoConnectClientSecretConfigured bool LinuxDoConnectRedirectURL string + // Generic OIDC OAuth 登录 + OIDCConnectEnabled bool + OIDCConnectProviderName string + OIDCConnectClientID string + OIDCConnectClientSecret string + OIDCConnectClientSecretConfigured bool + OIDCConnectIssuerURL string + OIDCConnectDiscoveryURL string + OIDCConnectAuthorizeURL string + OIDCConnectTokenURL string + OIDCConnectUserInfoURL string + OIDCConnectJWKSURL string + OIDCConnectScopes string + OIDCConnectRedirectURL string + OIDCConnectFrontendRedirectURL string + OIDCConnectTokenAuthMethod string + OIDCConnectUsePKCE bool + OIDCConnectValidateIDToken bool + OIDCConnectAllowedSigningAlgs string + OIDCConnectClockSkewSeconds int + OIDCConnectRequireEmailVerified bool + OIDCConnectUserInfoEmailPath string + OIDCConnectUserInfoIDPath string + OIDCConnectUserInfoUsernamePath string + SiteName string SiteLogo string SiteSubtitle string @@ -41,6 +66,8 @@ type SystemSettings struct { HideCcsImportButton bool PurchaseSubscriptionEnabled bool PurchaseSubscriptionURL string + TableDefaultPageSize int + TablePageSizeOptions []int CustomMenuItems string // JSON array of custom menu items CustomEndpoints string // JSON array of custom endpoints @@ -107,12 +134,17 @@ type PublicSettings struct { PurchaseSubscriptionEnabled bool PurchaseSubscriptionURL string + TableDefaultPageSize int + TablePageSizeOptions []int CustomMenuItems string // JSON array of custom menu items CustomEndpoints string // JSON array of custom endpoints - LinuxDoOAuthEnabled bool - BackendModeEnabled bool - Version string + LinuxDoOAuthEnabled bool + BackendModeEnabled bool + OIDCOAuthEnabled bool + OIDCOAuthProviderName string + PaymentEnabled bool + Version string } // StreamTimeoutSettings 流超时处理配置(仅控制超时后的处理方式,超时判定由网关配置控制) diff --git a/backend/internal/service/wire.go b/backend/internal/service/wire.go index d66d8cff86e574611ea46d6c30a034032f65afb9..a8ece8a38cecf90aece40a94e9ed5b24f33b5a2a 100644 --- a/backend/internal/service/wire.go +++ b/backend/internal/service/wire.go @@ -5,7 +5,9 @@ import ( "database/sql" "time" + dbent "github.com/Wei-Shaw/sub2api/ent" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/payment" "github.com/Wei-Shaw/sub2api/internal/pkg/logger" "github.com/google/wire" "github.com/redis/go-redis/v9" @@ -460,4 +462,20 @@ var ProviderSet = wire.NewSet( NewGroupCapacityService, NewChannelService, NewModelPricingResolver, + ProvidePaymentConfigService, + NewPaymentService, + ProvidePaymentOrderExpiryService, ) + +// ProvidePaymentConfigService wraps NewPaymentConfigService to accept the named +// payment.EncryptionKey type instead of raw []byte, avoiding Wire ambiguity. +func ProvidePaymentConfigService(entClient *dbent.Client, settingRepo SettingRepository, key payment.EncryptionKey) *PaymentConfigService { + return NewPaymentConfigService(entClient, settingRepo, []byte(key)) +} + +// ProvidePaymentOrderExpiryService creates and starts PaymentOrderExpiryService. +func ProvidePaymentOrderExpiryService(paymentSvc *PaymentService) *PaymentOrderExpiryService { + svc := NewPaymentOrderExpiryService(paymentSvc, 60*time.Second) + svc.Start() + return svc +} diff --git a/backend/migrations/091_add_group_messages_dispatch_model_config.sql b/backend/migrations/091_add_group_messages_dispatch_model_config.sql new file mode 100644 index 0000000000000000000000000000000000000000..8ddfcb0f72204888fb1ba0b9d342d444484acc51 --- /dev/null +++ b/backend/migrations/091_add_group_messages_dispatch_model_config.sql @@ -0,0 +1,2 @@ +ALTER TABLE groups +ADD COLUMN IF NOT EXISTS messages_dispatch_model_config JSONB NOT NULL DEFAULT '{}'::jsonb; diff --git a/backend/migrations/092_payment_orders.sql b/backend/migrations/092_payment_orders.sql new file mode 100644 index 0000000000000000000000000000000000000000..036e4dedc0dcb65d902e36f9ac0711ef7ce61515 --- /dev/null +++ b/backend/migrations/092_payment_orders.sql @@ -0,0 +1,47 @@ +CREATE TABLE IF NOT EXISTS payment_orders ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + user_email VARCHAR(255) NOT NULL DEFAULT '', + user_name VARCHAR(100) NOT NULL DEFAULT '', + user_notes TEXT, + amount DECIMAL(20,2) NOT NULL, + pay_amount DECIMAL(20,2) NOT NULL, + fee_rate DECIMAL(10,4) NOT NULL DEFAULT 0, + recharge_code VARCHAR(64) NOT NULL DEFAULT '', + payment_type VARCHAR(30) NOT NULL DEFAULT '', + payment_trade_no VARCHAR(128) NOT NULL DEFAULT '', + pay_url TEXT, + qr_code TEXT, + qr_code_img TEXT, + order_type VARCHAR(20) NOT NULL DEFAULT 'balance', + plan_id BIGINT, + subscription_group_id BIGINT, + subscription_days INT, + provider_instance_id VARCHAR(64), + status VARCHAR(30) NOT NULL DEFAULT 'PENDING', + refund_amount DECIMAL(20,2) NOT NULL DEFAULT 0, + refund_reason TEXT, + refund_at TIMESTAMPTZ, + force_refund BOOLEAN NOT NULL DEFAULT FALSE, + refund_requested_at TIMESTAMPTZ, + refund_request_reason TEXT, + refund_requested_by VARCHAR(20), + expires_at TIMESTAMPTZ NOT NULL, + paid_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + failed_at TIMESTAMPTZ, + failed_reason TEXT, + client_ip VARCHAR(50) NOT NULL DEFAULT '', + src_host VARCHAR(255) NOT NULL DEFAULT '', + src_url TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +-- Indexes +CREATE INDEX IF NOT EXISTS idx_payment_orders_user_id ON payment_orders(user_id); +CREATE INDEX IF NOT EXISTS idx_payment_orders_status ON payment_orders(status); +CREATE INDEX IF NOT EXISTS idx_payment_orders_expires_at ON payment_orders(expires_at); +CREATE INDEX IF NOT EXISTS idx_payment_orders_created_at ON payment_orders(created_at); +CREATE INDEX IF NOT EXISTS idx_payment_orders_paid_at ON payment_orders(paid_at); +CREATE INDEX IF NOT EXISTS idx_payment_orders_type_paid ON payment_orders(payment_type, paid_at); +CREATE INDEX IF NOT EXISTS idx_payment_orders_order_type ON payment_orders(order_type); diff --git a/backend/migrations/093_payment_audit_logs.sql b/backend/migrations/093_payment_audit_logs.sql new file mode 100644 index 0000000000000000000000000000000000000000..d05b15ef835e67b5a62f4c18aa03146f97130ca2 --- /dev/null +++ b/backend/migrations/093_payment_audit_logs.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS payment_audit_logs ( + id BIGSERIAL PRIMARY KEY, + order_id VARCHAR(64) NOT NULL, + action VARCHAR(50) NOT NULL, + detail TEXT NOT NULL DEFAULT '', + operator VARCHAR(100) NOT NULL DEFAULT 'system', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_payment_audit_logs_order_id ON payment_audit_logs(order_id); diff --git a/backend/migrations/094_removed_payment_channels.sql b/backend/migrations/094_removed_payment_channels.sql new file mode 100644 index 0000000000000000000000000000000000000000..cb20234795f0c3ab4535f2076c8d5585d775d4c8 --- /dev/null +++ b/backend/migrations/094_removed_payment_channels.sql @@ -0,0 +1,4 @@ +-- Migration 092: payment_channels table was removed before release. +-- This file is a no-op placeholder to maintain migration numbering continuity. +-- The payment system now uses the existing channels table (migration 081). +SELECT 1; diff --git a/backend/migrations/095_subscription_plans.sql b/backend/migrations/095_subscription_plans.sql new file mode 100644 index 0000000000000000000000000000000000000000..541d8f0c89aba0943415a26674d9b98a22b80fd5 --- /dev/null +++ b/backend/migrations/095_subscription_plans.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS subscription_plans ( + id BIGSERIAL PRIMARY KEY, + group_id BIGINT NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT NOT NULL DEFAULT '', + price DECIMAL(20,2) NOT NULL, + original_price DECIMAL(20,2), + validity_days INT NOT NULL DEFAULT 30, + validity_unit VARCHAR(10) NOT NULL DEFAULT 'day', + features TEXT NOT NULL DEFAULT '', + product_name VARCHAR(100) NOT NULL DEFAULT '', + for_sale BOOLEAN NOT NULL DEFAULT TRUE, + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_subscription_plans_group_id ON subscription_plans(group_id); +CREATE INDEX IF NOT EXISTS idx_subscription_plans_for_sale ON subscription_plans(for_sale); diff --git a/backend/migrations/096_payment_provider_instances.sql b/backend/migrations/096_payment_provider_instances.sql new file mode 100644 index 0000000000000000000000000000000000000000..bedd75df603408a1a47ab4cd60ff79487c117e14 --- /dev/null +++ b/backend/migrations/096_payment_provider_instances.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS payment_provider_instances ( + id BIGSERIAL PRIMARY KEY, + provider_key VARCHAR(30) NOT NULL, + name VARCHAR(100) NOT NULL DEFAULT '', + config TEXT NOT NULL, + supported_types VARCHAR(200) NOT NULL DEFAULT '', + enabled BOOLEAN NOT NULL DEFAULT TRUE, + sort_order INT NOT NULL DEFAULT 0, + limits TEXT NOT NULL DEFAULT '', + refund_enabled BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_payment_provider_instances_provider_key ON payment_provider_instances(provider_key); +CREATE INDEX IF NOT EXISTS idx_payment_provider_instances_enabled ON payment_provider_instances(enabled); diff --git a/backend/migrations/098_migrate_purchase_subscription_to_custom_menu.sql b/backend/migrations/098_migrate_purchase_subscription_to_custom_menu.sql new file mode 100644 index 0000000000000000000000000000000000000000..1864459ecca0abfb17c6d773b224f1feeb46f933 --- /dev/null +++ b/backend/migrations/098_migrate_purchase_subscription_to_custom_menu.sql @@ -0,0 +1,70 @@ +-- 096_migrate_purchase_subscription_to_custom_menu.sql +-- +-- Migrates the legacy purchase_subscription_url setting into custom_menu_items. +-- After migration, purchase_subscription_enabled is set to "false" and +-- purchase_subscription_url is cleared. +-- +-- Idempotent: skips if custom_menu_items already contains +-- "migrated_purchase_subscription". + +DO $$ +DECLARE + v_enabled text; + v_url text; + v_raw text; + v_items jsonb; + v_new_item jsonb; +BEGIN + -- Read legacy settings + SELECT value INTO v_enabled + FROM settings WHERE key = 'purchase_subscription_enabled'; + SELECT value INTO v_url + FROM settings WHERE key = 'purchase_subscription_url'; + + -- Skip if not enabled or URL is empty + IF COALESCE(v_enabled, '') <> 'true' OR COALESCE(TRIM(v_url), '') = '' THEN + RETURN; + END IF; + + -- Read current custom_menu_items + SELECT value INTO v_raw + FROM settings WHERE key = 'custom_menu_items'; + + IF COALESCE(v_raw, '') = '' OR v_raw = 'null' THEN + v_items := '[]'::jsonb; + ELSE + v_items := v_raw::jsonb; + END IF; + + -- Skip if already migrated (item with id "migrated_purchase_subscription" exists) + IF EXISTS ( + SELECT 1 FROM jsonb_array_elements(v_items) elem + WHERE elem ->> 'id' = 'migrated_purchase_subscription' + ) THEN + RETURN; + END IF; + + -- Build the new menu item + v_new_item := jsonb_build_object( + 'id', 'migrated_purchase_subscription', + 'label', 'Purchase', + 'icon_svg', '', + 'url', TRIM(v_url), + 'visibility', 'user', + 'sort_order', 100 + ); + + -- Append to array + v_items := v_items || jsonb_build_array(v_new_item); + + -- Upsert custom_menu_items + INSERT INTO settings (key, value) + VALUES ('custom_menu_items', v_items::text) + ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value; + + -- Clear legacy settings + UPDATE settings SET value = 'false' WHERE key = 'purchase_subscription_enabled'; + UPDATE settings SET value = '' WHERE key = 'purchase_subscription_url'; + + RAISE NOTICE '[migration-096] Migrated purchase_subscription_url (%) to custom_menu_items', v_url; +END $$; diff --git a/backend/migrations/099_fix_migrated_purchase_menu_label_icon.sql b/backend/migrations/099_fix_migrated_purchase_menu_label_icon.sql new file mode 100644 index 0000000000000000000000000000000000000000..5361ad816606bbf4133491a8e5d195364b5ce7c8 --- /dev/null +++ b/backend/migrations/099_fix_migrated_purchase_menu_label_icon.sql @@ -0,0 +1,51 @@ +-- 097_fix_migrated_purchase_menu_label_icon.sql +-- +-- Fixes the custom menu item created by migration 096: updates the label +-- from hardcoded English "Purchase" to "充值/订阅", and sets the icon_svg +-- to a credit-card SVG matching the sidebar CreditCardIcon. +-- +-- Idempotent: only modifies items where id = 'migrated_purchase_subscription'. + +DO $$ +DECLARE + v_raw text; + v_items jsonb; + v_idx int; + v_icon text; + v_elem jsonb; + v_i int := 0; +BEGIN + SELECT value INTO v_raw + FROM settings WHERE key = 'custom_menu_items'; + + IF COALESCE(v_raw, '') = '' OR v_raw = 'null' THEN + RETURN; + END IF; + + v_items := v_raw::jsonb; + + -- Find the index of the migrated item by iterating the array + v_idx := NULL; + FOR v_elem IN SELECT jsonb_array_elements(v_items) LOOP + IF v_elem ->> 'id' = 'migrated_purchase_subscription' THEN + v_idx := v_i; + EXIT; + END IF; + v_i := v_i + 1; + END LOOP; + + IF v_idx IS NULL THEN + RETURN; -- item not found, nothing to fix + END IF; + + -- Credit card SVG (Heroicons outline, matches CreditCardIcon in AppSidebar) + v_icon := ''; + + -- Update label and icon_svg + v_items := jsonb_set(v_items, ARRAY[v_idx::text, 'label'], '"充值/订阅"'::jsonb); + v_items := jsonb_set(v_items, ARRAY[v_idx::text, 'icon_svg'], to_jsonb(v_icon)); + + UPDATE settings SET value = v_items::text WHERE key = 'custom_menu_items'; + + RAISE NOTICE '[migration-097] Fixed migrated_purchase_subscription: label=充值/订阅, icon=CreditCard SVG'; +END $$; diff --git a/backend/migrations/100_remove_easypay_from_enabled_payment_types.sql b/backend/migrations/100_remove_easypay_from_enabled_payment_types.sql new file mode 100644 index 0000000000000000000000000000000000000000..8128ed09ff4cdbacd77bd65fe53d039e1513e79d --- /dev/null +++ b/backend/migrations/100_remove_easypay_from_enabled_payment_types.sql @@ -0,0 +1,17 @@ +-- 098_remove_easypay_from_enabled_payment_types.sql +-- +-- Removes "easypay" from ENABLED_PAYMENT_TYPES setting. +-- "easypay" is a provider key, not a payment type. Valid payment types +-- are: alipay, wxpay, alipay_direct, wxpay_direct, stripe. +-- +-- Idempotent: safe to run multiple times. + +UPDATE settings + SET value = array_to_string( + array_remove( + string_to_array(value, ','), + 'easypay' + ), ',' + ) + WHERE key = 'ENABLED_PAYMENT_TYPES' + AND value LIKE '%easypay%'; diff --git a/backend/migrations/101_add_payment_mode.sql b/backend/migrations/101_add_payment_mode.sql new file mode 100644 index 0000000000000000000000000000000000000000..eeb6ba7b2ff44b0250f200a82ffa01b606845df2 --- /dev/null +++ b/backend/migrations/101_add_payment_mode.sql @@ -0,0 +1,16 @@ +-- Add payment_mode field to payment_provider_instances +-- Values: 'redirect' (hosted page redirect), 'api' (API call for QR/payurl), '' (default/N/A) +ALTER TABLE payment_provider_instances ADD COLUMN IF NOT EXISTS payment_mode VARCHAR(20) NOT NULL DEFAULT ''; + +-- Migrate existing data: easypay instances with 'easypay' in supported_types → redirect mode +-- Remove 'easypay' from supported_types and set payment_mode = 'redirect' +UPDATE payment_provider_instances +SET payment_mode = 'redirect', + supported_types = TRIM(BOTH ',' FROM REPLACE(REPLACE(REPLACE( + supported_types, 'easypay,', ''), ',easypay', ''), 'easypay', '')) +WHERE provider_key = 'easypay' AND supported_types LIKE '%easypay%'; + +-- EasyPay instances without 'easypay' in supported_types → api mode +UPDATE payment_provider_instances +SET payment_mode = 'api' +WHERE provider_key = 'easypay' AND payment_mode = ''; diff --git a/backend/migrations/102_add_out_trade_no_to_payment_orders.sql b/backend/migrations/102_add_out_trade_no_to_payment_orders.sql new file mode 100644 index 0000000000000000000000000000000000000000..896c3c954de797a99a18113bf1972dd30a0fbe26 --- /dev/null +++ b/backend/migrations/102_add_out_trade_no_to_payment_orders.sql @@ -0,0 +1,6 @@ +-- 100_add_out_trade_no_to_payment_orders.sql +-- Adds out_trade_no column for external order ID used with payment providers. +-- Allows webhook handlers to look up orders by external ID instead of embedding DB ID. + +ALTER TABLE payment_orders ADD COLUMN IF NOT EXISTS out_trade_no VARCHAR(64) NOT NULL DEFAULT ''; +CREATE INDEX IF NOT EXISTS paymentorder_out_trade_no ON payment_orders (out_trade_no); diff --git a/deploy/codex-instructions.md.tmpl b/deploy/codex-instructions.md.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..87ad0a3d843d4792d03ee3a3bd357c33b4996a0f --- /dev/null +++ b/deploy/codex-instructions.md.tmpl @@ -0,0 +1,5 @@ +You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI on a user's computer. + +{{ if .ExistingInstructions }} +{{ .ExistingInstructions }} +{{ end }} diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml index 8f60acd5edc971d487512eb9bd0238ecd378f2e4..358f6a31d9db662dbe12fa74d9a832ccbe5a16ee 100644 --- a/deploy/config.example.yaml +++ b/deploy/config.example.yaml @@ -202,6 +202,32 @@ gateway: # # 注意:开启后会影响所有客户端的行为(不仅限于 VS Code / Codex CLI),请谨慎开启。 force_codex_cli: false + # Optional: template file used to build the final top-level Codex `instructions`. + # 可选:用于构建最终 Codex 顶层 `instructions` 的模板文件路径。 + # + # This is applied on the `/v1/messages -> Responses/Codex` conversion path, + # after Claude `system` has already been normalized into Codex `instructions`. + # 该模板作用于 `/v1/messages -> Responses/Codex` 转换链路,且发生在 Claude `system` + # 已经被归一化为 Codex `instructions` 之后。 + # + # The template can reference: + # 模板可引用: + # - {{ .ExistingInstructions }} : converted client instructions/system + # - {{ .OriginalModel }} : original requested model + # - {{ .NormalizedModel }} : normalized routing model + # - {{ .BillingModel }} : billing model + # - {{ .UpstreamModel }} : final upstream model + # + # If you want to preserve client system prompts, keep {{ .ExistingInstructions }} + # somewhere in the template. If omitted, the template output fully replaces it. + # 如需保留客户端 system 提示词,请在模板中显式包含 {{ .ExistingInstructions }}。 + # 若省略,则模板输出会完全覆盖它。 + # + # Docker users can mount a host file to /app/data/codex-instructions.md.tmpl + # and point this field there. + # Docker 用户可将宿主机文件挂载到 /app/data/codex-instructions.md.tmpl, + # 然后把本字段指向该路径。 + forced_codex_instructions_template_file: "" # OpenAI 透传模式是否放行客户端超时头(如 x-stainless-timeout) # 默认 false:过滤超时头,降低上游提前断流风险。 openai_passthrough_allow_timeout_headers: false @@ -820,6 +846,46 @@ linuxdo_connect: userinfo_id_path: "" userinfo_username_path: "" +# ============================================================================= +# Generic OIDC OAuth Login (SSO) +# 通用 OIDC OAuth 登录(用于 Sub2API 用户登录) +# ============================================================================= +oidc_connect: + enabled: false + provider_name: "OIDC" + client_id: "" + client_secret: "" + # 例如: "https://keycloak.example.com/realms/myrealm" + issuer_url: "" + # 可选: OIDC Discovery URL。为空时可手动填写 authorize/token/userinfo/jwks + discovery_url: "" + authorize_url: "" + token_url: "" + # 可选(仅补充 email/username,不用于 sub 可信绑定) + userinfo_url: "" + # validate_id_token=true 时必填 + jwks_url: "" + scopes: "openid email profile" + # 示例: "https://your-domain.com/api/v1/auth/oauth/oidc/callback" + redirect_url: "" + # 安全提示: + # - 建议使用同源相对路径(以 / 开头),避免把 token 重定向到意外的第三方域名 + # - 该地址不应包含 #fragment(本实现使用 URL fragment 传递 access_token) + frontend_redirect_url: "/auth/oidc/callback" + token_auth_method: "client_secret_post" # client_secret_post | client_secret_basic | none + # 注意:当 token_auth_method=none(public client)时,必须启用 PKCE + use_pkce: false + # 开启后强制校验 id_token 的签名和 claims(推荐) + validate_id_token: true + allowed_signing_algs: "RS256,ES256,PS256" + # 允许的时钟偏移(秒) + clock_skew_seconds: 120 + # 若 Provider 返回 email_verified=false,是否拒绝登录 + require_email_verified: false + userinfo_email_path: "" + userinfo_id_path: "" + userinfo_username_path: "" + # ============================================================================= # Default Settings # 默认设置 diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index a0bc1a606164f51c190213a6e134f563d713f97e..3a714260f20cf9125afe9e0da75f2d38251b13ae 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -31,6 +31,10 @@ services: # Optional: Mount custom config.yaml (uncomment and create the file first) # Copy config.example.yaml to config.yaml, modify it, then uncomment: # - ./config.yaml:/app/data/config.yaml + # Optional: Mount a custom Codex instructions template file, then point + # gateway.forced_codex_instructions_template_file at /app/data/codex-instructions.md.tmpl + # in config.yaml. + # - ./codex-instructions.md.tmpl:/app/data/codex-instructions.md.tmpl:ro environment: # ======================================================================= # Auto Setup (REQUIRED for Docker deployment) @@ -146,7 +150,17 @@ services: networks: - sub2api-network healthcheck: - test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://localhost:8080/health"] + test: + [ + "CMD", + "wget", + "-q", + "-T", + "5", + "-O", + "/dev/null", + "http://localhost:8080/health", + ] interval: 30s timeout: 10s retries: 3 @@ -177,11 +191,17 @@ services: networks: - sub2api-network healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}"] + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}", + ] interval: 10s timeout: 5s retries: 5 start_period: 10s + ports: + - 5432:5432 # 注意:不暴露端口到宿主机,应用通过内部网络连接 # 如需调试,可临时添加:ports: ["127.0.0.1:5433:5432"] @@ -199,12 +219,12 @@ services: volumes: - redis_data:/data command: > - sh -c ' - redis-server - --save 60 1 - --appendonly yes - --appendfsync everysec - ${REDIS_PASSWORD:+--requirepass "$REDIS_PASSWORD"}' + sh -c ' + redis-server + --save 60 1 + --appendonly yes + --appendfsync everysec + ${REDIS_PASSWORD:+--requirepass "$REDIS_PASSWORD"}' environment: - TZ=${TZ:-Asia/Shanghai} # REDISCLI_AUTH is used by redis-cli for authentication (safer than -a flag) @@ -217,7 +237,8 @@ services: timeout: 5s retries: 5 start_period: 5s - + ports: + - 6379:6379 # ============================================================================= # Volumes # ============================================================================= diff --git a/docs/PAYMENT.md b/docs/PAYMENT.md new file mode 100644 index 0000000000000000000000000000000000000000..b66a791c5f8392174cf2ea1b821c6603bfec10a5 --- /dev/null +++ b/docs/PAYMENT.md @@ -0,0 +1,273 @@ +# Payment System Configuration Guide + +Sub2API has a built-in payment system that enables user self-service top-up without deploying a separate payment service. + +--- + +## Table of Contents + +- [Supported Payment Methods](#supported-payment-methods) +- [Quick Start](#quick-start) +- [System Settings](#system-settings) +- [Provider Configuration](#provider-configuration) +- [Provider Instance Management](#provider-instance-management) +- [Webhook Configuration](#webhook-configuration) +- [Payment Flow](#payment-flow) +- [Migrating from Sub2ApiPay](#migrating-from-sub2apipay) + +--- + +## Supported Payment Methods + +| Provider | Payment Methods | Description | +|----------|----------------|-------------| +| **EasyPay** | Alipay, WeChat Pay | Third-party aggregation via EasyPay protocol | +| **Alipay (Direct)** | PC Page Pay, H5 Mobile Pay | Direct integration with Alipay Open Platform, auto-switches by device | +| **WeChat Pay (Direct)** | Native QR Code, H5 Pay | Direct integration with WeChat Pay APIv3, mobile-first H5 | +| **Stripe** | Card, Alipay, WeChat Pay, Link, etc. | International payments, multi-currency support | + +> Alipay/WeChat Pay direct and EasyPay can coexist. Direct channels connect to payment APIs directly with lower fees; EasyPay aggregates through third-party platforms with easier setup. + +> **EasyPay Recommendation**: [ZPay](https://z-pay.cn/?uid=23808) (`https://z-pay.cn/?uid=23808`) is recommended as an EasyPay provider (link contains the referral code of [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) original author [@touwaeriol](https://github.com/touwaeriol) — feel free to remove it). ZPay supports **individual users** (no business license required) with up to 10,000 CNY daily transactions; business-licensed accounts have no limit. Please evaluate the security, reliability, and compliance of any third-party payment provider on your own — this project does not endorse or guarantee any of them. + +--- + +## Quick Start + +1. Go to Admin Dashboard → **Settings** → **Payment Settings** tab +2. Enable **Payment** +3. Configure basic parameters (amount range, timeout, etc.) +4. Add at least one provider instance in **Provider Management** +5. Users can now top up from the frontend + +--- + +## System Settings + +Configure the following in Admin Dashboard **Settings → Payment Settings**: + +### Basic Settings + +| Setting | Description | Default | +|---------|-------------|---------| +| **Enable Payment** | Enable or disable the payment system | Off | +| **Product Name Prefix** | Prefix shown on payment page | - | +| **Product Name Suffix** | Suffix (e.g., "Credits") | - | +| **Minimum Amount** | Minimum single top-up amount | 1 | +| **Maximum Amount** | Maximum single top-up amount (empty = unlimited) | - | +| **Daily Limit** | Per-user daily cumulative limit (empty = unlimited) | - | +| **Order Timeout** | Order timeout in minutes (minimum 1) | 5 | +| **Max Pending Orders** | Maximum concurrent pending orders per user | 3 | +| **Load Balance Strategy** | Strategy for selecting provider instances | Least Amount | + +### Load Balance Strategies + +| Strategy | Description | +|----------|-------------| +| **Round Robin** | Distribute orders to instances in rotation | +| **Least Amount** | Prefer instances with the lowest daily cumulative amount | + +### Cancel Rate Limiting + +Prevents users from repeatedly creating and canceling orders: + +| Setting | Description | +|---------|-------------| +| **Enable Limit** | Toggle | +| **Window Mode** | Sliding / Fixed window | +| **Time Window** | Window duration | +| **Window Unit** | Minutes / Hours | +| **Max Cancels** | Maximum cancellations allowed within the window | + +### Help Information + +| Setting | Description | +|---------|-------------| +| **Help Image** | Customer service QR code or help image (supports upload) | +| **Help Text** | Instructions displayed on the payment page | + +--- + +## Provider Configuration + +Each provider type requires different credentials. Select the type when adding a new provider instance in **Provider Management → Add Provider**. + +> **Callback URLs are auto-generated**: When adding a provider, the Notify URL and Return URL are automatically constructed from your site domain. You only need to confirm the domain is correct. + +### EasyPay + +Compatible with any payment service that implements the EasyPay protocol. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Merchant ID (PID)** | EasyPay merchant ID | Yes | +| **Merchant Key (PKey)** | EasyPay merchant secret key | Yes | +| **API Base URL** | EasyPay API base address | Yes | +| **Alipay Channel ID** | Specify Alipay channel (optional) | No | +| **WeChat Channel ID** | Specify WeChat channel (optional) | No | + +### Alipay (Direct) + +Direct integration with Alipay Open Platform. Supports PC page pay and H5 mobile pay. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **AppID** | Alipay application AppID | Yes | +| **Private Key** | RSA2 application private key | Yes | +| **Alipay Public Key** | Alipay public key | Yes | + +### WeChat Pay (Direct) + +Direct integration with WeChat Pay APIv3. Supports Native QR code and H5 payment. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **AppID** | WeChat Pay AppID | Yes | +| **Merchant ID (MchID)** | WeChat Pay merchant ID | Yes | +| **Merchant API Private Key** | Merchant API private key (PEM format) | Yes | +| **APIv3 Key** | 32-byte APIv3 key | Yes | +| **WeChat Pay Public Key** | WeChat Pay public key (PEM format) | Yes | +| **WeChat Pay Public Key ID** | WeChat Pay public key ID | No | +| **Certificate Serial Number** | Merchant certificate serial number | No | + +### Stripe + +International payment platform supporting multiple payment methods and currencies. + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Secret Key** | Stripe secret key (`sk_live_...` or `sk_test_...`) | Yes | +| **Publishable Key** | Stripe publishable key (`pk_live_...` or `pk_test_...`) | Yes | +| **Webhook Secret** | Stripe Webhook signing secret (`whsec_...`) | Yes | + +--- + +## Provider Instance Management + +You can create **multiple instances** of the same provider type for load balancing and risk control: + +- **Multi-instance load balancing** — Distribute orders via round-robin or least-amount strategy +- **Independent limits** — Each instance can have its own min/max amount and daily limit +- **Independent toggle** — Enable/disable individual instances without affecting others +- **Refund control** — Enable or disable refunds per instance +- **Payment methods** — Each instance can support a subset of payment methods +- **Ordering** — Drag to reorder instances + +### Instance Limit Configuration + +Each instance supports these limits: + +| Limit | Description | +|-------|-------------| +| **Minimum Amount** | Minimum order amount accepted by this instance | +| **Maximum Amount** | Maximum order amount accepted by this instance | +| **Daily Limit** | Daily cumulative transaction limit for this instance | + +> During load balancing, instances that exceed their limits are automatically skipped. + +--- + +## Webhook Configuration + +Payment callbacks are essential for the payment system to work correctly. + +### Callback URL Format + +When adding a provider, the system auto-generates callback URLs from your site domain: + +| Provider | Callback Path | +|----------|-------------| +| **EasyPay** | `https://your-domain.com/api/v1/payment/webhook/easypay` | +| **Alipay (Direct)** | `https://your-domain.com/api/v1/payment/webhook/alipay` | +| **WeChat Pay (Direct)** | `https://your-domain.com/api/v1/payment/webhook/wxpay` | +| **Stripe** | `https://your-domain.com/api/v1/payment/webhook/stripe` | + +> Replace `your-domain.com` with your actual domain. For EasyPay / Alipay / WeChat Pay, the callback URL is auto-filled when adding the provider — no manual configuration needed. + +### Stripe Webhook Setup + +1. Log in to [Stripe Dashboard](https://dashboard.stripe.com/) +2. Go to **Developers → Webhooks** +3. Add an endpoint with the callback URL +4. Subscribe to events: `payment_intent.succeeded`, `payment_intent.payment_failed` +5. Copy the generated Webhook Secret (`whsec_...`) to your provider configuration + +### Important Notes + +- Callback URLs must use **HTTPS** (required by Stripe, strongly recommended for others) +- Ensure your firewall allows callback requests from payment platforms +- The system automatically verifies callback signatures to prevent forgery +- Balance top-up is processed automatically upon successful payment — no manual intervention needed + +--- + +## Payment Flow + +``` +User selects amount and payment method + │ + ▼ + Create Order (PENDING) + ├─ Validate amount range, pending order count, daily limit + ├─ Load balance to select provider instance + └─ Call provider to get payment info + │ + ▼ + User completes payment + ├─ EasyPay → QR code / H5 redirect + ├─ Alipay → PC page pay / H5 mobile pay + ├─ WeChat Pay → Native QR / H5 pay + └─ Stripe → Payment Element (card/Alipay/WeChat/etc.) + │ + ▼ + Webhook callback verified → Order PAID + │ + ▼ + Auto top-up to user balance → Order COMPLETED +``` + +### Order Status Reference + +| Status | Description | +|--------|-------------| +| `PENDING` | Waiting for user to complete payment | +| `PAID` | Payment confirmed, awaiting balance credit | +| `COMPLETED` | Balance credited successfully | +| `EXPIRED` | Timed out without payment | +| `CANCELLED` | Cancelled by user | +| `FAILED` | Balance credit failed, admin can retry | +| `REFUND_REQUESTED` | Refund requested | +| `REFUNDING` | Refund in progress | +| `REFUNDED` | Refund completed | + +### Timeout and Fallback + +- Before marking an order as expired, the background job queries the upstream payment status first +- If the user has actually paid but the callback was delayed, the system will reconcile automatically +- The background job runs every 60 seconds to check for timed-out orders + +--- + +## Migrating from Sub2ApiPay + +If you previously used [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) as an external payment system, you can migrate to the built-in payment system: + +### Key Differences + +| Aspect | Sub2ApiPay | Built-in Payment | +|--------|-----------|-----------------| +| Deployment | Separate service (Next.js + PostgreSQL) | Built into Sub2API, no extra deployment | +| Payment Methods | EasyPay, Alipay, WeChat, Stripe | Same | +| Configuration | Environment variables + separate admin UI | Unified in Sub2API admin dashboard | +| Top-up Integration | Via Admin API callback | Internal processing, more reliable | +| Subscription Plans | Supported | Not yet (planned) | +| Order Management | Separate admin interface | Integrated in Sub2API admin dashboard | + +### Migration Steps + +1. Enable payment in Sub2API admin dashboard and configure providers (use the same payment credentials) +2. Update webhook callback URLs to Sub2API's callback endpoints +3. Verify that new orders are processed correctly via built-in payment +4. Decommission the Sub2ApiPay service + +> **Note**: Historical order data from Sub2ApiPay will not be automatically migrated. Keep Sub2ApiPay running for a while to access historical records. diff --git a/docs/PAYMENT_CN.md b/docs/PAYMENT_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..9d96557f68356dbc16a9041483e48b6a744f5fcb --- /dev/null +++ b/docs/PAYMENT_CN.md @@ -0,0 +1,273 @@ +# 支付系统配置指南 + +Sub2API 内置支付系统,支持用户自助充值,无需部署独立的支付服务。 + +--- + +## 目录 + +- [支持的支付方式](#支持的支付方式) +- [快速开始](#快速开始) +- [系统设置](#系统设置) +- [服务商配置](#服务商配置) +- [服务商实例管理](#服务商实例管理) +- [Webhook 配置](#webhook-配置) +- [支付流程](#支付流程) +- [从 Sub2ApiPay 迁移](#从-sub2apipay-迁移) + +--- + +## 支持的支付方式 + +| 服务商 | 支付方式 | 说明 | +|--------|---------|------| +| **EasyPay(易支付)** | 支付宝、微信支付 | 兼容易支付协议的第三方聚合支付 | +| **支付宝官方** | 支付宝 PC 页面支付、H5 手机网站支付 | 直接对接支付宝开放平台,自动根据终端切换 | +| **微信官方** | Native 扫码支付、H5 支付 | 直接对接微信支付 APIv3,移动端优先 H5 | +| **Stripe** | 银行卡、支付宝、微信支付、Link 等 | 国际支付,支持多币种 | + +> 支付宝官方 / 微信官方与 EasyPay 可以共存。官方渠道直接对接 API,资金直达商户账户,手续费更低;EasyPay 通过第三方平台聚合,接入门槛更低。 + +> **EasyPay 推荐**:个人推荐 [ZPay](https://z-pay.cn/?uid=23808)(`https://z-pay.cn/?uid=23808`)作为 EasyPay 服务商(链接含 [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) 原作者 [@touwaeriol](https://github.com/touwaeriol) 的邀请码,介意可去掉)。ZPay 支持**个人用户**(无营业执照)每日 1 万元以内交易;拥有营业执照则无限额。支付渠道的安全性、稳定性及合规性请自行鉴别,本项目不对任何第三方支付服务商做担保或背书。 + +--- + +## 快速开始 + +1. 进入管理后台 → **设置** → **支付设置** 标签页 +2. 开启 **启用支付** +3. 配置基本参数(金额范围、超时时间等) +4. 在 **服务商管理** 中添加至少一个服务商实例 +5. 用户即可在前端页面进行充值 + +--- + +## 系统设置 + +在管理后台 **设置 → 支付设置** 中配置以下参数: + +### 基本设置 + +| 设置项 | 说明 | 默认值 | +|--------|------|--------| +| **启用支付** | 启用或禁用支付系统 | 关闭 | +| **商品名前缀** | 支付页面显示的商品名前缀 | - | +| **商品名后缀** | 商品名后缀(如"元") | - | +| **最低金额** | 单笔最低充值金额 | 1 | +| **最高金额** | 单笔最高充值金额(留空表示不限制) | - | +| **每日限额** | 每用户每日累计充值上限(留空表示不限制) | - | +| **订单超时时间** | 订单超时分钟数,至少 1 分钟 | 5 | +| **最大待支付订单数** | 同一用户最大并行待支付订单数 | 3 | +| **负载均衡策略** | 多服务商实例时的选择策略 | 最少金额 | + +### 负载均衡策略 + +| 策略 | 说明 | +|------|------| +| **轮询(round-robin)** | 按顺序轮流分配到各服务商实例 | +| **最少金额(least-amount)** | 优先分配到当日累计金额最少的实例 | + +### 取消频率限制 + +防止用户频繁创建并取消订单: + +| 设置项 | 说明 | +|--------|------| +| **启用限制** | 开关 | +| **窗口模式** | 滚动窗口 / 固定窗口 | +| **时间窗口** | 窗口长度 | +| **窗口单位** | 分钟 / 小时 | +| **最大次数** | 窗口内允许的最大取消次数 | + +### 帮助信息 + +| 设置项 | 说明 | +|--------|------| +| **帮助图片** | 充值页面显示的客服二维码等图片(支持上传) | +| **帮助文本** | 充值页面显示的说明文字 | + +--- + +## 服务商配置 + +每种服务商需要不同的凭证和参数。在 **服务商管理 → 添加服务商** 中选择类型后填写。 + +> **回调地址自动生成**:添加服务商时,异步回调地址(Notify URL)和同步跳转地址(Return URL)由系统根据你的站点域名自动拼接,无需手动填写。管理员只需确认域名正确即可。 + +### EasyPay(易支付) + +兼容任何 EasyPay 协议的支付服务商。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **商户 ID(PID)** | EasyPay 商户 ID | 是 | +| **商户密钥(PKey)** | EasyPay 商户密钥 | 是 | +| **API 地址** | EasyPay API 基础地址 | 是 | +| **支付宝通道 ID** | 指定支付宝通道(可选) | 否 | +| **微信通道 ID** | 指定微信通道(可选) | 否 | + +### 支付宝官方 + +直接对接支付宝开放平台,支持 PC 页面支付和 H5 手机网站支付。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **AppID** | 支付宝应用 AppID | 是 | +| **应用私钥** | RSA2 应用私钥 | 是 | +| **支付宝公钥** | 支付宝公钥 | 是 | + +### 微信官方 + +直接对接微信支付 APIv3,支持 Native 扫码支付和 H5 支付。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **AppID** | 微信支付 AppID | 是 | +| **商户号(MchID)** | 微信支付商户号 | 是 | +| **商户 API 私钥** | 商户 API 私钥(PEM 格式) | 是 | +| **APIv3 密钥** | 32 位 APIv3 密钥 | 是 | +| **微信支付公钥** | 微信支付公钥(PEM 格式) | 是 | +| **微信支付公钥 ID** | 微信支付公钥 ID | 否 | +| **商户证书序列号** | 商户证书序列号 | 否 | + +### Stripe + +国际支付平台,支持多种支付方式和币种。 + +| 参数 | 说明 | 必填 | +|------|------|------| +| **Secret Key** | Stripe 密钥(`sk_live_...` 或 `sk_test_...`) | 是 | +| **Publishable Key** | Stripe 可公开密钥(`pk_live_...` 或 `pk_test_...`) | 是 | +| **Webhook Secret** | Stripe Webhook 签名密钥(`whsec_...`) | 是 | + +--- + +## 服务商实例管理 + +同一种服务商可以创建**多个实例**,实现负载均衡和风控: + +- **多实例负载均衡** — 按轮询或最少金额策略分流订单 +- **独立限额** — 每个实例可独立配置单笔最小/最大金额和每日限额 +- **独立启停** — 可单独启用/禁用某个实例,不影响其他实例 +- **退款控制** — 每个实例可单独开启或关闭退款功能 +- **支付方式** — 每个实例可选择支持的支付方式子集 +- **排序** — 拖拽调整实例顺序 + +### 实例限额配置 + +每个实例支持以下限额: + +| 限额项 | 说明 | +|--------|------| +| **单笔最小金额** | 该实例接受的最小订单金额 | +| **单笔最大金额** | 该实例接受的最大订单金额 | +| **每日限额** | 该实例每日累计交易上限 | + +> 负载均衡时,系统会自动跳过超出限额的实例。 + +--- + +## Webhook 配置 + +支付回调是支付系统的核心环节,必须正确配置: + +### 回调地址格式 + +添加服务商时,系统会自动根据站点域名拼接回调地址,格式如下: + +| 服务商 | 回调路径 | +|--------|---------| +| **EasyPay** | `https://your-domain.com/api/v1/payment/webhook/easypay` | +| **支付宝官方** | `https://your-domain.com/api/v1/payment/webhook/alipay` | +| **微信官方** | `https://your-domain.com/api/v1/payment/webhook/wxpay` | +| **Stripe** | `https://your-domain.com/api/v1/payment/webhook/stripe` | + +> 将 `your-domain.com` 替换为你的实际域名。EasyPay / 支付宝 / 微信的回调地址在添加服务商时自动填入,无需手动配置。 + +### Stripe Webhook 设置 + +1. 登录 [Stripe Dashboard](https://dashboard.stripe.com/) +2. 进入 **Developers → Webhooks** +3. 添加端点,填写回调地址 +4. 订阅事件:`payment_intent.succeeded`、`payment_intent.payment_failed` +5. 将生成的 Webhook Secret(`whsec_...`)填入服务商配置 + +### 注意事项 + +- 回调地址必须是 **HTTPS**(Stripe 强制要求,其他服务商强烈推荐) +- 确保服务器防火墙允许支付平台的回调请求 +- 系统会自动进行签名验证,防止伪造回调 +- 支付成功后自动完成余额充值,无需人工干预 + +--- + +## 支付流程 + +``` +用户选择充值金额和支付方式 + │ + ▼ + 创建订单 (PENDING) + ├─ 校验金额范围、待支付订单数、每日限额 + ├─ 负载均衡选择服务商实例 + └─ 调用服务商获取支付信息 + │ + ▼ + 用户完成支付 + ├─ EasyPay → 扫码 / H5 跳转 + ├─ 支付宝官方 → PC 页面支付 / H5 手机网站支付 + ├─ 微信官方 → Native 扫码 / H5 支付 + └─ Stripe → Payment Element(银行卡/支付宝/微信等) + │ + ▼ + 支付回调验签 → 订单 PAID + │ + ▼ + 自动充值到用户余额 → 订单 COMPLETED +``` + +### 订单状态说明 + +| 状态 | 说明 | +|------|------| +| `PENDING` | 待支付,等待用户完成支付 | +| `PAID` | 已支付,等待充值到账 | +| `COMPLETED` | 已完成,余额已到账 | +| `EXPIRED` | 已过期,超时未支付 | +| `CANCELLED` | 已取消,用户主动取消 | +| `FAILED` | 充值失败,可管理员重试 | +| `REFUND_REQUESTED` | 已申请退款 | +| `REFUNDING` | 退款处理中 | +| `REFUNDED` | 已退款 | + +### 超时与兜底 + +- 订单超时后,后台任务会先查询上游支付状态再标记过期 +- 如果用户实际已支付但回调延迟,系统会通过查询补单 +- 后台任务每 60 秒执行一次超时检查 + +--- + +## 从 Sub2ApiPay 迁移 + +如果你之前使用 [Sub2ApiPay](https://github.com/touwaeriol/sub2apipay) 作为外部支付系统,现在可以迁移到内置支付: + +### 主要差异 + +| 对比项 | Sub2ApiPay | 内置支付 | +|--------|-----------|---------| +| 部署方式 | 独立服务(Next.js + PostgreSQL) | 内置于 Sub2API,无需额外部署 | +| 支付方式 | EasyPay、支付宝、微信、Stripe | 相同 | +| 配置方式 | 环境变量 + 独立管理后台 | Sub2API 管理后台内统一配置 | +| 充值对接 | 通过 Admin API 回调 | 内部直接处理,更可靠 | +| 订阅套餐 | 支持 | 暂不支持(计划中) | +| 订单管理 | 独立管理界面 | 集成在 Sub2API 管理后台 | + +### 迁移步骤 + +1. 在 Sub2API 管理后台启用支付并配置服务商(使用相同的支付凭证) +2. 更新 Webhook 回调地址为 Sub2API 的回调地址 +3. 确认新订单通过内置支付正常处理 +4. 停用 Sub2ApiPay 服务 + +> **注意**:Sub2ApiPay 中的历史订单数据不会自动迁移。建议保留 Sub2ApiPay 一段时间以便查询历史记录。 diff --git a/frontend/package.json b/frontend/package.json index d2a6deded711d97decc0f73b42ae1429600fe871..a220d3a7fdd693a920af974f1a71765e86097d15 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,9 +16,10 @@ }, "dependencies": { "@lobehub/icons": "^4.0.2", + "@stripe/stripe-js": "^9.0.1", "@tanstack/vue-virtual": "^3.13.23", "@vueuse/core": "^10.7.0", - "axios": "^1.13.5", + "axios": "^1.15.0", "chart.js": "^4.4.1", "dompurify": "^3.3.1", "driver.js": "^1.4.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 505b72f388765694056841ece820078c485e4879..0a7b3fa1cbe276307d45f1c43a64d32ff7a75e3c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@lobehub/icons': specifier: ^4.0.2 version: 4.0.2(@lobehub/ui@4.9.2)(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@stripe/stripe-js': + specifier: ^9.0.1 + version: 9.0.1 '@tanstack/vue-virtual': specifier: ^3.13.23 version: 3.13.23(vue@3.5.26(typescript@5.6.3)) @@ -18,8 +21,8 @@ importers: specifier: ^10.7.0 version: 10.11.1(vue@3.5.26(typescript@5.6.3)) axios: - specifier: ^1.13.5 - version: 1.13.5 + specifier: ^1.15.0 + version: 1.15.0 chart.js: specifier: ^4.4.1 version: 4.5.1 @@ -134,11 +137,11 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@ant-design/colors@8.0.0': - resolution: {integrity: sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==} + '@ant-design/colors@8.0.1': + resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} - '@ant-design/cssinjs-utils@2.0.2': - resolution: {integrity: sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==} + '@ant-design/cssinjs-utils@2.1.2': + resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==} peerDependencies: react: '>=18' react-dom: '>=18' @@ -149,15 +152,21 @@ packages: react: '>=16.0.0' react-dom: '>=16.0.0' - '@ant-design/fast-color@3.0.0': - resolution: {integrity: sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==} + '@ant-design/cssinjs@2.1.2': + resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@3.0.1': + resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} engines: {node: '>=8.x'} '@ant-design/icons-svg@4.4.2': resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} - '@ant-design/icons@6.1.0': - resolution: {integrity: sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==} + '@ant-design/icons@6.1.1': + resolution: {integrity: sha512-AMT4N2y++TZETNHiM77fs4a0uPVCJGuL5MTonk13Pvv7UN7sID1cNEZOc1qNqx6zLKAOilTEFAdAoAFKa0U//Q==} engines: {node: '>=8'} peerDependencies: react: '>=16.0.0' @@ -208,6 +217,10 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -220,8 +233,8 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.0.0': - resolution: {integrity: sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg==} + '@base-ui/react@1.3.0': + resolution: {integrity: sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17 || ^18 || ^19 @@ -231,8 +244,8 @@ packages: '@types/react': optional: true - '@base-ui/utils@0.2.3': - resolution: {integrity: sha512-/CguQ2PDaOzeVOkllQR8nocJ0FFIDqsWIcURsVmm53QGo8NhFNpePjNlyPIB41luxfOqnG7PU0xicMEw3ls7XQ==} + '@base-ui/utils@0.2.6': + resolution: {integrity: sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw==} peerDependencies: '@types/react': ^17 || ^18 || ^19 react: ^17 || ^18 || ^19 @@ -244,23 +257,23 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@braintree/sanitize-url@7.1.1': - resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@chevrotain/cst-dts-gen@11.0.3': - resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + '@chevrotain/cst-dts-gen@12.0.0': + resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==} - '@chevrotain/gast@11.0.3': - resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + '@chevrotain/gast@12.0.0': + resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==} - '@chevrotain/regexp-to-ast@11.0.3': - resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + '@chevrotain/regexp-to-ast@12.0.0': + resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==} - '@chevrotain/types@11.0.3': - resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + '@chevrotain/types@12.0.0': + resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==} - '@chevrotain/utils@11.0.3': - resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@chevrotain/utils@12.0.0': + resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==} '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} @@ -536,26 +549,26 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@floating-ui/core@1.7.3': - resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - '@floating-ui/dom@1.7.4': - resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - '@floating-ui/react-dom@2.1.6': - resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react@0.27.16': - resolution: {integrity: sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==} + '@floating-ui/react@0.27.19': + resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==} peerDependencies: react: '>=17.0.0' react-dom: '>=17.0.0' - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} '@giscus/react@3.1.0': resolution: {integrity: sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg==} @@ -618,8 +631,8 @@ packages: '@kurkle/color@0.3.4': resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} - '@lit-labs/ssr-dom-shim@1.5.0': - resolution: {integrity: sha512-HLomZXMmrCFHSRKESF5vklAKsDY7/fsT/ZhqCu3V0UoW/Qbv8wxmO4W9bx4KnCCF2Zak4yuk+AGraK/bPmI4kA==} + '@lit-labs/ssr-dom-shim@1.5.1': + resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==} '@lit/reactive-element@2.1.2': resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==} @@ -660,8 +673,8 @@ packages: '@types/react': '>=16' react: '>=16' - '@mermaid-js/parser@0.6.3': - resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@mermaid-js/parser@1.1.0': + resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -682,8 +695,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@primer/octicons@19.21.1': - resolution: {integrity: sha512-7tgtBkCNcg75YJnckinzvES+uxysYQCe+CHSEnzr3VYgxttzKRvfmrnVogl3aEuHCQP4xhiE9k2lFDhYwGtTzQ==} + '@primer/octicons@19.23.1': + resolution: {integrity: sha512-CzjGmxkmNhyst6EekrS3SJPdtzgIkUMP/LSJch65y99/kmiFXbO1a+q7zoYe3hnI9NaOM0IN+ydDIbOmd8YqcA==} '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -929,8 +942,8 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@rc-component/async-validator@5.0.4': - resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} + '@rc-component/async-validator@5.1.0': + resolution: {integrity: sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==} engines: {node: '>=14.x'} '@rc-component/cascader@1.10.0': @@ -981,8 +994,8 @@ packages: react: '>=16.11.0' react-dom: '>=16.11.0' - '@rc-component/form@1.6.0': - resolution: {integrity: sha512-A7vrN8kExtw4sW06mrsgCb1rowhvBFFvQU6Bk/NL0Fj6Wet/5GF0QnGCxBu/sG3JI9FEhsJWES0D44BW2d0hzg==} + '@rc-component/form@1.6.2': + resolution: {integrity: sha512-OgIn2RAoaSBqaIgzJf/X6iflIa9LpTozci1lagLBdURDFhGA370v0+T0tXxOi8YShMjTha531sFhwtnrv+EJaQ==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -1018,8 +1031,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - '@rc-component/mini-decimal@1.1.0': - resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + '@rc-component/mini-decimal@1.1.3': + resolution: {integrity: sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==} engines: {node: '>=8.x'} '@rc-component/motion@1.1.6': @@ -1054,8 +1067,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - '@rc-component/picker@1.9.0': - resolution: {integrity: sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==} + '@rc-component/picker@1.9.1': + resolution: {integrity: sha512-9FBYYsvH3HMLICaPDA/1Th5FLaDkFa7qAtangIdlhKb3ZALaR745e9PsOhheJb6asS4QXc12ffiAcjdkZ4C5/g==} engines: {node: '>=12.x'} peerDependencies: date-fns: '>= 2.x' @@ -1108,8 +1121,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - '@rc-component/resize-observer@1.0.1': - resolution: {integrity: sha512-r+w+Mz1EiueGk1IgjB3ptNXLYSLZ5vnEfKHH+gfgj7JMupftyzvUUl3fRcMZe5uMM04x0n8+G2o/c6nlO2+Wag==} + '@rc-component/resize-observer@1.1.2': + resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -1193,15 +1206,15 @@ packages: react: '*' react-dom: '*' - '@rc-component/trigger@2.3.0': - resolution: {integrity: sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==} + '@rc-component/trigger@2.3.1': + resolution: {integrity: sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' - '@rc-component/trigger@3.8.1': - resolution: {integrity: sha512-walnDJnKq+OcPQFHBMN+YZmdHV8+6z75+Rgpc0dW1c+Dmy6O7tRueDs4LdbwjlryQfTdsw84PIkNPzcx5yQ7qQ==} + '@rc-component/trigger@3.9.0': + resolution: {integrity: sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==} engines: {node: '>=8.x'} peerDependencies: react: '>=18.0.0' @@ -1213,6 +1226,12 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' + '@rc-component/util@1.10.1': + resolution: {integrity: sha512-q++9S6rUa5Idb/xIBNz6jtvumw5+O5YV5V0g4iK9mn9jWs4oGJheE3ZN1kAnE723AXyaD8v95yeOASmdk8Jnng==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + '@rc-component/util@1.7.0': resolution: {integrity: sha512-tIvIGj4Vl6fsZFvWSkYw9sAfiCKUXMyhVz6kpKyZbwyZyRPqv2vxYZROdaO1VB4gqTNvUZFXh6i3APUiterw5g==} peerDependencies: @@ -1347,26 +1366,26 @@ packages: cpu: [x64] os: [win32] - '@shikijs/core@3.20.0': - resolution: {integrity: sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g==} + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} - '@shikijs/engine-javascript@3.20.0': - resolution: {integrity: sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg==} + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} - '@shikijs/engine-oniguruma@3.20.0': - resolution: {integrity: sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==} + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - '@shikijs/langs@3.20.0': - resolution: {integrity: sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==} + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} - '@shikijs/themes@3.20.0': - resolution: {integrity: sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==} + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} - '@shikijs/transformers@3.20.0': - resolution: {integrity: sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g==} + '@shikijs/transformers@3.23.0': + resolution: {integrity: sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==} - '@shikijs/types@3.20.0': - resolution: {integrity: sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==} + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -1379,6 +1398,10 @@ packages: peerDependencies: react: '>= 16.3.0' + '@stripe/stripe-js@9.0.1': + resolution: {integrity: sha512-un0URSosrW7wNr7xZ5iI2mC9mdeXZ3KERoVlA2RdmeLXYxHUPXq0yHzir2n/MtyXXEdSaELtz4WXGS6dzPEeKA==} + engines: {node: '>=12.16'} + '@tanstack/virtual-core@3.13.23': resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==} @@ -1459,8 +1482,8 @@ packages: '@types/d3-selection@3.0.11': resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} - '@types/d3-shape@3.1.7': - resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} '@types/d3-time-format@4.0.3': resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} @@ -1480,8 +1503,8 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} '@types/dompurify@3.2.0': resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==} @@ -1505,8 +1528,8 @@ packages: '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - '@types/katex@0.16.7': - resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -1605,6 +1628,9 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@use-gesture/core@10.3.1': resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} @@ -1736,6 +1762,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + adler-32@1.3.1: resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} engines: {node: '>=0.8'} @@ -1744,8 +1775,8 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ahooks@3.9.6: - resolution: {integrity: sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==} + ahooks@3.9.7: + resolution: {integrity: sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1827,8 +1858,8 @@ packages: peerDependencies: postcss: ^8.1.0 - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} @@ -1924,13 +1955,14 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} - chevrotain-allstar@0.3.1: - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + chevrotain-allstar@0.4.1: + resolution: {integrity: sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==} peerDependencies: - chevrotain: ^11.0.0 + chevrotain: ^12.0.0 - chevrotain@11.0.3: - resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + chevrotain@12.0.0: + resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==} + engines: {node: '>=22.0.0'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -1952,10 +1984,6 @@ packages: cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2056,8 +2084,8 @@ packages: peerDependencies: cytoscape: ^3.2.0 - cytoscape@3.33.1: - resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + cytoscape@3.33.2: + resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==} engines: {node: '>=0.10'} d3-array@2.12.1: @@ -2116,8 +2144,8 @@ packages: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} engines: {node: '>=12'} - d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} engines: {node: '>=12'} d3-geo@3.1.1: @@ -2199,15 +2227,15 @@ packages: resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} engines: {node: '>=12'} - dagre-d3-es@7.0.13: - resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -2228,8 +2256,8 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decode-named-character-reference@1.2.0: - resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} decode-uri-component@0.4.1: resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==} @@ -2242,8 +2270,8 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - delaunator@5.0.1: - resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -2276,6 +2304,9 @@ packages: dompurify@3.3.1: resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.3.3: + resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + driver.js@1.4.0: resolution: {integrity: sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==} @@ -2336,8 +2367,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.43.0: - resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} + es-toolkit@1.45.1: + resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -2531,8 +2562,8 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - framer-motion@12.23.26: - resolution: {integrity: sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==} + framer-motion@12.38.0: + resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2560,8 +2591,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -2707,8 +2738,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immer@11.1.3: - resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} + immer@11.1.4: + resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -2882,8 +2913,8 @@ packages: json2mq@0.2.0: resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} - katex@0.16.27: - resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} + katex@0.16.45: + resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} hasBin: true keyv@4.5.4: @@ -2892,9 +2923,9 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - langium@3.3.1: - resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} - engines: {node: '>=16.0.0'} + langium@4.2.2: + resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==} + engines: {node: '>=20.10.0', npm: '>=10.2.3'} layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -2936,11 +2967,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - - lodash-es@4.17.22: - resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2948,6 +2976,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2998,6 +3029,11 @@ packages: engines: {node: '>= 20'} hasBin: true + marked@17.0.6: + resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -3005,8 +3041,8 @@ packages: mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} @@ -3064,8 +3100,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.12.2: - resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + mermaid@11.14.0: + resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -3225,14 +3261,14 @@ packages: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} engines: {node: '>=0.10.0'} - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - motion-dom@12.23.23: - resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==} + motion-dom@12.38.0: + resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} - motion-utils@12.23.6: - resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + motion-utils@12.36.0: + resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} motion@12.23.26: resolution: {integrity: sha512-Ll8XhVxY8LXMVYTCfme27WH2GjBrCIzY4+ndr5QKxsK+YwCtOi2B/oBi5jcIbik5doXuWT/4KKDOVAZJkeY5VQ==} @@ -3308,8 +3344,8 @@ packages: oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} - oniguruma-to-es@4.3.4: - resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + oniguruma-to-es@4.3.5: + resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -3503,8 +3539,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} @@ -3617,8 +3654,8 @@ packages: peerDependencies: react: ^19.2.3 - react-draggable@4.4.6: - resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==} + react-draggable@4.5.0: + resolution: {integrity: sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==} peerDependencies: react: '>= 16.3.0' react-dom: '>= 16.3.0' @@ -3629,17 +3666,16 @@ packages: peerDependencies: react: '>= 16.8' - react-error-boundary@6.0.1: - resolution: {integrity: sha512-zArgQpjJUN1ZLMEKWtifxQweW3yfvwL5j2nh3Pesze1qG6r5oCDMy/TA97bUF01wy4xCeeL4/pd8GHmvEsP3Bg==} + react-error-boundary@6.1.1: + resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} peerDependencies: react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-hotkeys-hook@5.2.1: - resolution: {integrity: sha512-xbKh6zJxd/vJHT4Bw4+0pBD662Fk20V+VFhLqciCg+manTVO4qlqRqiwFOYelfHN9dBvWj9vxaPkSS26ZSIJGg==} + react-hotkeys-hook@5.2.4: + resolution: {integrity: sha512-BgKg+A1+TawkYluh5Bo4cTmcgMN5L29uhJbDUQdHwPX+qgXRjIPYU5kIDHyxnAwCkCBiu9V5OpB2mpyeluVF2A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -3664,8 +3700,8 @@ packages: react: optional: true - react-rnd@10.5.2: - resolution: {integrity: sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==} + react-rnd@10.5.3: + resolution: {integrity: sha512-s/sIT3pGZnQ+57egijkTp9mizjIWrJz68Pq6yd+F/wniFY3IriML18dUXnQe/HP9uMiJ+9MAp44hljG99fZu6Q==} peerDependencies: react: '>=16.3.0' react-dom: '>=16.3.0' @@ -3795,8 +3831,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - robust-predicates@3.0.2: - resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} rollup@4.54.0: resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} @@ -3858,19 +3894,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki-stream@0.1.3: - resolution: {integrity: sha512-pDIqmaP/zJWHNV8bJKp0tD0CZ6OkF+lWTIvmNRLktlTjBjN3+durr19JarS657U1oSEf/WrSYmdzwr9CeD6m2Q==} + shiki-stream@0.1.4: + resolution: {integrity: sha512-4pz6JGSDmVTTkPJ/ueixHkFAXY4ySCc+unvCaDZV7hqq/sdJZirRxgIXSuNSKgiFlGTgRR97sdu2R8K55sPsrw==} peerDependencies: react: ^19.0.0 + solid-js: ^1.9.0 vue: ^3.2.0 peerDependenciesMeta: react: optional: true + solid-js: + optional: true vue: optional: true - shiki@3.20.0: - resolution: {integrity: sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg==} + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3967,8 +4006,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.3.8: - resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -4010,8 +4049,8 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} engines: {node: '>=18'} tinyglobby@0.2.15: @@ -4087,8 +4126,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -4121,8 +4160,8 @@ packages: unist-util-visit-parents@6.0.2: resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} @@ -4289,9 +4328,6 @@ packages: resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} hasBin: true - vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -4487,15 +4523,15 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@ant-design/colors@8.0.0': + '@ant-design/colors@8.0.1': dependencies: - '@ant-design/fast-color': 3.0.0 + '@ant-design/fast-color': 3.0.1 - '@ant-design/cssinjs-utils@2.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@babel/runtime': 7.28.4 - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@babel/runtime': 7.29.2 + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -4511,22 +4547,34 @@ snapshots: react-dom: 19.2.3(react@19.2.3) stylis: 4.3.6 - '@ant-design/fast-color@3.0.0': {} + '@ant-design/cssinjs@2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.29.2 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + clsx: 2.1.1 + csstype: 3.2.3 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + stylis: 4.3.6 + + '@ant-design/fast-color@3.0.1': {} '@ant-design/icons-svg@4.4.2': {} - '@ant-design/icons@6.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@ant-design/icons@6.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@ant-design/colors': 8.0.0 + '@ant-design/colors': 8.0.1 '@ant-design/icons-svg': 4.4.2 - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@ant-design/react-slick@2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 clsx: 2.1.1 json2mq: 0.2.0 react: 19.2.3 @@ -4536,7 +4584,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.0.2 + tinyexec: 1.1.1 '@asamuzakjp/css-color@3.2.0': dependencies: @@ -4579,6 +4627,8 @@ snapshots: '@babel/runtime@7.28.4': {} + '@babel/runtime@7.29.2': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -4602,24 +4652,23 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@base-ui/react@1.3.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 - '@base-ui/utils': 0.2.3(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@floating-ui/utils': 0.2.10 + '@babel/runtime': 7.29.2 + '@base-ui/utils': 0.2.6(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@floating-ui/utils': 0.2.11 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - reselect: 5.1.1 tabbable: 6.4.0 use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: '@types/react': 19.2.7 - '@base-ui/utils@0.2.3(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@base-ui/utils@0.2.6(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 - '@floating-ui/utils': 0.2.10 + '@babel/runtime': 7.29.2 + '@floating-ui/utils': 0.2.11 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) reselect: 5.1.1 @@ -4629,24 +4678,22 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@braintree/sanitize-url@7.1.1': {} + '@braintree/sanitize-url@7.1.2': {} - '@chevrotain/cst-dts-gen@11.0.3': + '@chevrotain/cst-dts-gen@12.0.0': dependencies: - '@chevrotain/gast': 11.0.3 - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + '@chevrotain/gast': 12.0.0 + '@chevrotain/types': 12.0.0 - '@chevrotain/gast@11.0.3': + '@chevrotain/gast@12.0.0': dependencies: - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + '@chevrotain/types': 12.0.0 - '@chevrotain/regexp-to-ast@11.0.3': {} + '@chevrotain/regexp-to-ast@12.0.0': {} - '@chevrotain/types@11.0.3': {} + '@chevrotain/types@12.0.0': {} - '@chevrotain/utils@11.0.3': {} + '@chevrotain/utils@12.0.0': {} '@csstools/color-helpers@5.1.0': {} @@ -4881,30 +4928,30 @@ snapshots: '@eslint/js@8.57.1': {} - '@floating-ui/core@1.7.3': + '@floating-ui/core@1.7.5': dependencies: - '@floating-ui/utils': 0.2.10 + '@floating-ui/utils': 0.2.11 - '@floating-ui/dom@1.7.4': + '@floating-ui/dom@1.7.6': dependencies: - '@floating-ui/core': 1.7.3 - '@floating-ui/utils': 0.2.10 + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 - '@floating-ui/react-dom@2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@floating-ui/react-dom@2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/dom': 1.7.4 + '@floating-ui/dom': 1.7.6 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@floating-ui/react@0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@floating-ui/react@0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@floating-ui/utils': 0.2.10 + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@floating-ui/utils': 0.2.11 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) tabbable: 6.4.0 - '@floating-ui/utils@0.2.10': {} + '@floating-ui/utils@0.2.11': {} '@giscus/react@3.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: @@ -4930,7 +4977,7 @@ snapshots: dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/types': 2.0.0 - mlly: 1.8.0 + mlly: 1.8.2 '@intlify/core-base@9.14.5': dependencies: @@ -4971,11 +5018,11 @@ snapshots: '@kurkle/color@0.3.4': {} - '@lit-labs/ssr-dom-shim@1.5.0': {} + '@lit-labs/ssr-dom-shim@1.5.1': {} '@lit/reactive-element@2.1.2': dependencies: - '@lit-labs/ssr-dom-shim': 1.5.0 + '@lit-labs/ssr-dom-shim': 1.5.1 '@lobehub/emojilib@1.0.0': {} @@ -4984,7 +5031,7 @@ snapshots: '@lobehub/emojilib': 1.0.0 antd-style: 4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) emoji-regex: 10.6.0 - es-toolkit: 1.43.0 + es-toolkit: 1.45.1 lucide-react: 0.562.0(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5009,8 +5056,8 @@ snapshots: '@lobehub/ui@4.9.2(@lobehub/fluent-emoji@4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@lobehub/icons@4.0.2)(@types/mdast@4.0.4)(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(micromark-util-types@2.0.2)(micromark@4.0.2)(motion@12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vue@3.5.26(typescript@5.6.3))': dependencies: - '@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@base-ui/react': 1.0.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@base-ui/react': 1.3.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) @@ -5018,32 +5065,32 @@ snapshots: '@emoji-mart/data': 1.2.1 '@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@19.2.3) '@emotion/is-prop-valid': 1.4.0 - '@floating-ui/react': 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@floating-ui/react': 0.27.19(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@giscus/react': 3.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@lobehub/fluent-emoji': 4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@lobehub/icons': 4.0.2(@lobehub/ui@4.9.2)(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@mdx-js/mdx': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.3) '@radix-ui/react-slot': 1.2.4(@types/react@19.2.7)(react@19.2.3) - '@shikijs/core': 3.20.0 - '@shikijs/transformers': 3.20.0 + '@shikijs/core': 3.23.0 + '@shikijs/transformers': 3.23.0 '@splinetool/runtime': 0.9.526 - ahooks: 3.9.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + ahooks: 3.9.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) antd: 6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) antd-style: 4.1.0(@types/react@19.2.7)(antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) chroma-js: 3.2.0 class-variance-authority: 0.7.1 clsx: 2.1.1 - dayjs: 1.11.19 + dayjs: 1.11.20 emoji-mart: 5.6.0 - es-toolkit: 1.43.0 + es-toolkit: 1.45.1 fast-deep-equal: 3.1.3 - immer: 11.1.3 - katex: 0.16.27 + immer: 11.1.4 + katex: 0.16.45 leva: 0.10.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) lucide-react: 0.562.0(react@19.2.3) - marked: 17.0.1 - mermaid: 11.12.2 + marked: 17.0.6 + mermaid: 11.14.0 motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) numeral: 2.0.6 polished: 4.3.1 @@ -5057,11 +5104,11 @@ snapshots: react: 19.2.3 react-avatar-editor: 14.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react-dom: 19.2.3(react@19.2.3) - react-error-boundary: 6.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react-hotkeys-hook: 5.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-error-boundary: 6.1.1(react@19.2.3) + react-hotkeys-hook: 5.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react-markdown: 10.1.0(@types/react@19.2.7)(react@19.2.3) react-merge-refs: 3.0.2(react@19.2.3) - react-rnd: 10.5.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-rnd: 10.5.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react-zoom-pan-pinch: 3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rehype-github-alerts: 4.2.0 rehype-katex: 7.0.1 @@ -5071,9 +5118,9 @@ snapshots: remark-gfm: 4.0.1 remark-github: 12.0.0 remark-math: 6.0.0 - shiki: 3.20.0 - shiki-stream: 0.1.3(react@19.2.3)(vue@3.5.26(typescript@5.6.3)) - swr: 2.3.8(react@19.2.3) + shiki: 3.23.0 + shiki-stream: 0.1.4(react@19.2.3)(vue@3.5.26(typescript@5.6.3)) + swr: 2.4.1(react@19.2.3) ts-md5: 2.0.1 unified: 11.0.5 url-join: 5.0.0 @@ -5085,6 +5132,7 @@ snapshots: - '@types/react-dom' - micromark - micromark-util-types + - solid-js - supports-color - vue @@ -5094,7 +5142,7 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdx': 2.0.13 - acorn: 8.15.0 + acorn: 8.16.0 collapse-white-space: 2.1.0 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 @@ -5103,7 +5151,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 markdown-extensions: 2.0.0 recma-build-jsx: 1.0.0 - recma-jsx: 1.0.1(acorn@8.15.0) + recma-jsx: 1.0.1(acorn@8.16.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 remark-mdx: 3.1.1 @@ -5113,7 +5161,7 @@ snapshots: unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color @@ -5124,9 +5172,9 @@ snapshots: '@types/react': 19.2.7 react: 19.2.3 - '@mermaid-js/parser@0.6.3': + '@mermaid-js/parser@1.1.0': dependencies: - langium: 3.3.1 + langium: 4.2.2 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -5145,7 +5193,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@primer/octicons@19.21.1': + '@primer/octicons@19.23.1': dependencies: object-assign: 4.1.1 @@ -5192,7 +5240,7 @@ snapshots: '@radix-ui/react-popper@1.2.8(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-arrow': 1.1.7(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) @@ -5341,46 +5389,46 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rc-component/async-validator@5.0.4': + '@rc-component/async-validator@5.1.0': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/cascader@1.10.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/select': 1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/tree': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/checkbox@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/collapse@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/color-picker@3.0.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@ant-design/fast-color': 3.0.0 - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@ant-design/fast-color': 3.0.1 + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/context@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5388,7 +5436,7 @@ snapshots: dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5397,23 +5445,23 @@ snapshots: dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/dropdown@1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@rc-component/form@1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@rc-component/form@1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/async-validator': 5.0.4 - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/async-validator': 5.1.0 + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5422,22 +5470,22 @@ snapshots: dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/input-number@1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/mini-decimal': 1.1.0 - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/mini-decimal': 1.1.3 + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/input@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5447,8 +5495,8 @@ snapshots: '@rc-component/input': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/menu': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/textarea': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5457,68 +5505,68 @@ snapshots: dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@rc-component/mini-decimal@1.1.0': + '@rc-component/mini-decimal@1.1.3': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/motion@1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/mutate-observer@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/notification@1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/overflow@1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@babel/runtime': 7.29.2 + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/pagination@1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@rc-component/picker@1.9.0(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@rc-component/picker@1.9.1(dayjs@1.11.20)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - dayjs: 1.11.19 + dayjs: 1.11.20 '@rc-component/portal@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 @@ -5526,42 +5574,42 @@ snapshots: '@rc-component/portal@2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/progress@1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/qrcode@1.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/rate@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@rc-component/resize-observer@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@rc-component/resize-observer@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/segmented@1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5569,8 +5617,8 @@ snapshots: '@rc-component/select@1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/virtual-list': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 @@ -5578,21 +5626,21 @@ snapshots: '@rc-component/slider@1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/steps@1.2.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/switch@1.0.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5600,8 +5648,8 @@ snapshots: '@rc-component/table@1.9.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/context': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/virtual-list': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 @@ -5612,8 +5660,8 @@ snapshots: '@rc-component/dropdown': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/menu': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5621,16 +5669,16 @@ snapshots: '@rc-component/textarea@1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/input': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/tooltip@1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5638,8 +5686,8 @@ snapshots: '@rc-component/tour@2.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5648,7 +5696,7 @@ snapshots: dependencies: '@rc-component/select': 1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/tree': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5656,15 +5704,15 @@ snapshots: '@rc-component/tree@1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/virtual-list': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@rc-component/trigger@2.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@rc-component/trigger@2.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/portal': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -5673,23 +5721,30 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@rc-component/trigger@3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@rc-component/trigger@3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@rc-component/motion': 1.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/portal': 2.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) '@rc-component/upload@1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + '@rc-component/util@1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + is-mobile: 5.0.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-is: 18.3.1 + '@rc-component/util@1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: is-mobile: 5.0.0 @@ -5699,9 +5754,9 @@ snapshots: '@rc-component/virtual-list@1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@babel/runtime': 7.28.4 - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@babel/runtime': 7.29.2 + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -5772,38 +5827,38 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.54.0': optional: true - '@shikijs/core@3.20.0': + '@shikijs/core@3.23.0': dependencies: - '@shikijs/types': 3.20.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.20.0': + '@shikijs/engine-javascript@3.23.0': dependencies: - '@shikijs/types': 3.20.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.4 + oniguruma-to-es: 4.3.5 - '@shikijs/engine-oniguruma@3.20.0': + '@shikijs/engine-oniguruma@3.23.0': dependencies: - '@shikijs/types': 3.20.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.20.0': + '@shikijs/langs@3.23.0': dependencies: - '@shikijs/types': 3.20.0 + '@shikijs/types': 3.23.0 - '@shikijs/themes@3.20.0': + '@shikijs/themes@3.23.0': dependencies: - '@shikijs/types': 3.20.0 + '@shikijs/types': 3.23.0 - '@shikijs/transformers@3.20.0': + '@shikijs/transformers@3.23.0': dependencies: - '@shikijs/core': 3.20.0 - '@shikijs/types': 3.20.0 + '@shikijs/core': 3.23.0 + '@shikijs/types': 3.23.0 - '@shikijs/types@3.20.0': + '@shikijs/types@3.23.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -5819,6 +5874,8 @@ snapshots: dependencies: react: 19.2.3 + '@stripe/stripe-js@9.0.1': {} + '@tanstack/virtual-core@3.13.23': {} '@tanstack/vue-virtual@3.13.23(vue@3.5.26(typescript@5.6.3))': @@ -5891,7 +5948,7 @@ snapshots: '@types/d3-selection@3.0.11': {} - '@types/d3-shape@3.1.7': + '@types/d3-shape@3.1.8': dependencies: '@types/d3-path': 3.1.1 @@ -5936,14 +5993,14 @@ snapshots: '@types/d3-scale': 4.0.9 '@types/d3-scale-chromatic': 3.1.0 '@types/d3-selection': 3.0.11 - '@types/d3-shape': 3.1.7 + '@types/d3-shape': 3.1.8 '@types/d3-time': 3.0.4 '@types/d3-time-format': 4.0.3 '@types/d3-timer': 3.0.2 '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 - '@types/debug@4.1.12': + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -5967,7 +6024,7 @@ snapshots: '@types/js-cookie@3.0.6': {} - '@types/katex@0.16.7': {} + '@types/katex@0.16.8': {} '@types/mdast@4.0.4': dependencies: @@ -6084,6 +6141,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@use-gesture/core@10.3.1': {} '@use-gesture/react@10.3.1(react@19.2.3)': @@ -6270,20 +6332,26 @@ snapshots: dependencies: acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn@8.15.0: {} + acorn@8.16.0: {} + adler-32@1.3.1: {} agent-base@7.1.4: {} - ahooks@3.9.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + ahooks@3.9.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@types/js-cookie': 3.0.6 - dayjs: 1.11.19 + dayjs: 1.11.20 intersection-observer: 0.12.2 js-cookie: 3.0.5 - lodash: 4.17.21 + lodash: 4.18.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) react-fast-compare: 3.2.2 @@ -6329,13 +6397,13 @@ snapshots: antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@ant-design/colors': 8.0.0 - '@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@ant-design/cssinjs-utils': 2.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@ant-design/fast-color': 3.0.0 - '@ant-design/icons': 6.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@ant-design/colors': 8.0.1 + '@ant-design/cssinjs': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@ant-design/fast-color': 3.0.1 + '@ant-design/icons': 6.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@ant-design/react-slick': 2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/cascader': 1.10.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/checkbox': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/collapse': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -6343,7 +6411,7 @@ snapshots: '@rc-component/dialog': 1.5.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/drawer': 1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/dropdown': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/form': 1.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/form': 1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/image': 1.5.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/input': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/input-number': 1.6.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -6353,11 +6421,11 @@ snapshots: '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/notification': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/pagination': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/picker': 1.9.0(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/picker': 1.9.1(dayjs@1.11.20)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/progress': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/qrcode': 1.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/rate': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/resize-observer': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/segmented': 1.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/select': 1.4.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/slider': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -6370,11 +6438,11 @@ snapshots: '@rc-component/tour': 2.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/tree': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/tree-select': 1.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/trigger': 3.8.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/trigger': 3.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@rc-component/upload': 1.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@rc-component/util': 1.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rc-component/util': 1.10.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 - dayjs: 1.11.19 + dayjs: 1.11.20 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) scroll-into-view-if-needed: 3.1.0 @@ -6416,11 +6484,11 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 - axios@1.13.5: + axios@1.15.0: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 - proxy-from-env: 1.1.0 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -6510,19 +6578,18 @@ snapshots: check-error@2.1.3: {} - chevrotain-allstar@0.3.1(chevrotain@11.0.3): + chevrotain-allstar@0.4.1(chevrotain@12.0.0): dependencies: - chevrotain: 11.0.3 - lodash-es: 4.17.22 + chevrotain: 12.0.0 + lodash-es: 4.18.1 - chevrotain@11.0.3: + chevrotain@12.0.0: dependencies: - '@chevrotain/cst-dts-gen': 11.0.3 - '@chevrotain/gast': 11.0.3 - '@chevrotain/regexp-to-ast': 11.0.3 - '@chevrotain/types': 11.0.3 - '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 + '@chevrotain/cst-dts-gen': 12.0.0 + '@chevrotain/gast': 12.0.0 + '@chevrotain/regexp-to-ast': 12.0.0 + '@chevrotain/types': 12.0.0 + '@chevrotain/utils': 12.0.0 chokidar@3.6.0: dependencies: @@ -6554,8 +6621,6 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - clsx@1.2.1: {} - clsx@2.1.1: {} codepage@1.15.0: {} @@ -6630,17 +6695,17 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2): dependencies: cose-base: 1.0.3 - cytoscape: 3.33.1 + cytoscape: 3.33.2 - cytoscape-fcose@2.2.0(cytoscape@3.33.1): + cytoscape-fcose@2.2.0(cytoscape@3.33.2): dependencies: cose-base: 2.2.0 - cytoscape: 3.33.1 + cytoscape: 3.33.2 - cytoscape@3.33.1: {} + cytoscape@3.33.2: {} d3-array@2.12.1: dependencies: @@ -6672,7 +6737,7 @@ snapshots: d3-delaunay@6.0.4: dependencies: - delaunator: 5.0.1 + delaunator: 5.1.0 d3-dispatch@3.0.1: {} @@ -6699,7 +6764,7 @@ snapshots: d3-quadtree: 3.0.1 d3-timer: 3.0.1 - d3-format@3.1.0: {} + d3-format@3.1.2: {} d3-geo@3.1.1: dependencies: @@ -6734,7 +6799,7 @@ snapshots: d3-scale@4.0.2: dependencies: d3-array: 3.2.4 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 @@ -6791,7 +6856,7 @@ snapshots: d3-ease: 3.0.1 d3-fetch: 3.0.1 d3-force: 3.0.0 - d3-format: 3.1.0 + d3-format: 3.1.2 d3-geo: 3.1.1 d3-hierarchy: 3.1.2 d3-interpolate: 3.0.1 @@ -6809,17 +6874,17 @@ snapshots: d3-transition: 3.0.1(d3-selection@3.0.0) d3-zoom: 3.0.0 - dagre-d3-es@7.0.13: + dagre-d3-es@7.0.14: dependencies: d3: 7.9.0 - lodash-es: 4.17.22 + lodash-es: 4.18.1 data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - dayjs@1.11.19: {} + dayjs@1.11.20: {} de-indent@1.0.2: {} @@ -6831,7 +6896,7 @@ snapshots: decimal.js@10.6.0: {} - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -6841,9 +6906,9 @@ snapshots: deep-is@0.1.4: {} - delaunator@5.0.1: + delaunator@5.1.0: dependencies: - robust-predicates: 3.0.2 + robust-predicates: 3.0.3 delayed-stream@1.0.0: {} @@ -6871,6 +6936,10 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dompurify@3.3.3: + optionalDependencies: + '@types/trusted-types': 2.0.7 + driver.js@1.4.0: {} dunder-proto@1.0.1: @@ -6923,7 +6992,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.43.0: {} + es-toolkit@1.45.1: {} esast-util-from-estree@2.0.0: dependencies: @@ -6935,7 +7004,7 @@ snapshots: esast-util-from-js@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - acorn: 8.15.0 + acorn: 8.16.0 esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 @@ -7180,10 +7249,10 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + framer-motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - motion-dom: 12.23.23 - motion-utils: 12.23.6 + motion-dom: 12.38.0 + motion-utils: 12.36.0 tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.4.0 @@ -7199,7 +7268,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} + get-east-asian-width@1.5.0: {} get-intrinsic@1.3.0: dependencies: @@ -7334,7 +7403,7 @@ snapshots: mdast-util-to-hast: 13.2.1 parse5: 7.3.0 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -7459,7 +7528,7 @@ snapshots: ignore@5.3.2: {} - immer@11.1.3: {} + immer@11.1.4: {} import-fresh@3.3.1: dependencies: @@ -7625,7 +7694,7 @@ snapshots: dependencies: string-convert: 0.2.1 - katex@0.16.27: + katex@0.16.45: dependencies: commander: 8.3.0 @@ -7635,13 +7704,14 @@ snapshots: khroma@2.1.0: {} - langium@3.3.1: + langium@4.2.2: dependencies: - chevrotain: 11.0.3 - chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + '@chevrotain/regexp-to-ast': 12.0.0 + chevrotain: 12.0.0 + chevrotain-allstar: 0.4.1(chevrotain@12.0.0) vscode-languageserver: 9.0.1 vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.0.8 + vscode-uri: 3.1.0 layout-base@1.0.2: {} @@ -7677,7 +7747,7 @@ snapshots: lit-element@4.2.2: dependencies: - '@lit-labs/ssr-dom-shim': 1.5.0 + '@lit-labs/ssr-dom-shim': 1.5.1 '@lit/reactive-element': 2.1.2 lit-html: 3.3.2 @@ -7699,14 +7769,14 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.21: {} - - lodash-es@4.17.22: {} + lodash-es@4.18.1: {} lodash.merge@4.6.2: {} lodash@4.17.21: {} + lodash@4.18.1: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -7747,6 +7817,8 @@ snapshots: marked@17.0.1: {} + marked@17.0.6: {} + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -7756,11 +7828,11 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@2.0.2: + mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -7785,7 +7857,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: @@ -7794,7 +7866,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7804,7 +7876,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7813,14 +7885,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.1.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.1.0 mdast-util-gfm-strikethrough: 2.0.0 @@ -7836,7 +7908,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 longest-streak: 3.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 unist-util-remove-position: 5.0.0 transitivePeerDependencies: @@ -7848,7 +7920,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7861,7 +7933,7 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -7872,7 +7944,7 @@ snapshots: mdast-util-mdx@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.2.0 mdast-util-mdxjs-esm: 2.0.1 @@ -7886,7 +7958,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7910,7 +7982,7 @@ snapshots: micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -7922,7 +7994,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -7938,23 +8010,24 @@ snapshots: merge2@1.4.1: {} - mermaid@11.12.2: + mermaid@11.14.0: dependencies: - '@braintree/sanitize-url': 7.1.1 + '@braintree/sanitize-url': 7.1.2 '@iconify/utils': 3.1.0 - '@mermaid-js/parser': 0.6.3 + '@mermaid-js/parser': 1.1.0 '@types/d3': 7.4.3 - cytoscape: 3.33.1 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) - cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.33.2 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2) + cytoscape-fcose: 2.2.0(cytoscape@3.33.2) d3: 7.9.0 d3-sankey: 0.12.3 - dagre-d3-es: 7.0.13 - dayjs: 1.11.19 - dompurify: 3.3.1 - katex: 0.16.27 + dagre-d3-es: 7.0.14 + dayjs: 1.11.20 + dompurify: 3.3.3 + katex: 0.16.45 khroma: 2.1.0 - lodash-es: 4.17.22 + lodash-es: 4.18.1 marked: 16.4.2 roughjs: 4.6.6 stylis: 4.3.6 @@ -7963,7 +8036,7 @@ snapshots: micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -7982,7 +8055,7 @@ snapshots: micromark-extension-cjk-friendly-util@2.1.1(micromark-util-types@2.0.2): dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.5.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 optionalDependencies: @@ -8059,9 +8132,9 @@ snapshots: micromark-extension-math@3.1.0: dependencies: - '@types/katex': 0.16.7 + '@types/katex': 0.16.8 devlop: 1.1.0 - katex: 0.16.27 + katex: 0.16.45 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -8109,8 +8182,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) micromark-extension-mdx-expression: 3.0.1 micromark-extension-mdx-jsx: 3.0.2 micromark-extension-mdx-md: 2.0.0 @@ -8188,7 +8261,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -8234,9 +8307,9 @@ snapshots: micromark@4.0.2: dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -8284,22 +8357,22 @@ snapshots: for-in: 1.0.2 is-extendable: 1.0.1 - mlly@1.8.0: + mlly@1.8.2: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.1 + ufo: 1.6.3 - motion-dom@12.23.23: + motion-dom@12.38.0: dependencies: - motion-utils: 12.23.6 + motion-utils: 12.36.0 - motion-utils@12.23.6: {} + motion-utils@12.36.0: {} motion@12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - framer-motion: 12.23.26(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.4.0 @@ -8353,7 +8426,7 @@ snapshots: oniguruma-parser@0.12.1: {} - oniguruma-to-es@4.3.4: + oniguruma-to-es@4.3.5: dependencies: oniguruma-parser: 0.12.1 regex: 6.1.0 @@ -8399,7 +8472,7 @@ snapshots: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -8465,7 +8538,7 @@ snapshots: pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.8.0 + mlly: 1.8.2 pathe: 2.0.3 pngjs@5.0.0: {} @@ -8530,7 +8603,7 @@ snapshots: proto-list@1.2.4: {} - proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} psl@1.15.0: dependencies: @@ -8556,7 +8629,7 @@ snapshots: rc-collapse@4.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -8565,7 +8638,7 @@ snapshots: rc-dialog@9.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/portal': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -8575,14 +8648,14 @@ snapshots: rc-footer@0.6.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) rc-image@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 '@rc-component/portal': 1.1.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) classnames: 2.5.1 rc-dialog: 9.6.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -8593,8 +8666,8 @@ snapshots: rc-input-number@9.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 - '@rc-component/mini-decimal': 1.1.0 + '@babel/runtime': 7.29.2 + '@rc-component/mini-decimal': 1.1.3 classnames: 2.5.1 rc-input: 1.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -8603,7 +8676,7 @@ snapshots: rc-input@1.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 @@ -8611,8 +8684,8 @@ snapshots: rc-menu@9.16.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 - '@rc-component/trigger': 2.3.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@babel/runtime': 7.29.2 + '@rc-component/trigger': 2.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-overflow: 1.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -8622,7 +8695,7 @@ snapshots: rc-motion@2.9.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 @@ -8630,7 +8703,7 @@ snapshots: rc-overflow@1.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 rc-resize-observer: 1.4.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -8639,7 +8712,7 @@ snapshots: rc-resize-observer@1.4.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 classnames: 2.5.1 rc-util: 5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 @@ -8648,7 +8721,7 @@ snapshots: rc-util@5.44.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.2 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) react-is: 18.3.1 @@ -8673,9 +8746,9 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 - react-draggable@4.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-draggable@4.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - clsx: 1.2.1 + clsx: 2.1.1 prop-types: 15.8.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -8687,14 +8760,13 @@ snapshots: prop-types: 15.8.1 react: 19.2.3 - react-error-boundary@6.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-error-boundary@6.1.1(react@19.2.3): dependencies: react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) react-fast-compare@3.2.2: {} - react-hotkeys-hook@5.2.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-hotkeys-hook@5.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -8716,7 +8788,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.2 unified: 11.0.5 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color @@ -8725,12 +8797,12 @@ snapshots: optionalDependencies: react: 19.2.3 - react-rnd@10.5.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-rnd@10.5.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: re-resizable: 6.11.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - react-draggable: 4.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react-draggable: 4.5.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tslib: 2.6.2 react-zoom-pan-pinch@3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): @@ -8756,10 +8828,10 @@ snapshots: estree-util-build-jsx: 3.0.1 vfile: 6.0.3 - recma-jsx@1.0.1(acorn@8.15.0): + recma-jsx@1.0.1(acorn@8.16.0): dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) estree-util-to-js: 2.0.0 recma-parse: 1.0.0 recma-stringify: 1.0.0 @@ -8791,18 +8863,18 @@ snapshots: rehype-github-alerts@4.2.0: dependencies: - '@primer/octicons': 19.21.1 + '@primer/octicons': 19.23.1 hast-util-from-html: 2.0.3 hast-util-is-element: 3.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 rehype-katex@7.0.1: dependencies: '@types/hast': 3.0.4 - '@types/katex': 0.16.7 + '@types/katex': 0.16.8 hast-util-from-html-isomorphic: 2.0.0 hast-util-to-text: 4.0.2 - katex: 0.16.27 + katex: 0.16.45 unist-util-visit-parents: 6.0.2 vfile: 6.0.3 @@ -8853,7 +8925,7 @@ snapshots: mdast-util-find-and-replace: 3.0.2 mdast-util-to-string: 4.0.0 to-vfile: 8.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 remark-math@6.0.0: @@ -8875,7 +8947,7 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -8919,7 +8991,7 @@ snapshots: dependencies: glob: 7.2.3 - robust-predicates@3.0.2: {} + robust-predicates@3.0.3: {} rollup@4.54.0: dependencies: @@ -8999,21 +9071,21 @@ snapshots: shebang-regex@3.0.0: {} - shiki-stream@0.1.3(react@19.2.3)(vue@3.5.26(typescript@5.6.3)): + shiki-stream@0.1.4(react@19.2.3)(vue@3.5.26(typescript@5.6.3)): dependencies: - '@shikijs/core': 3.20.0 + '@shikijs/core': 3.23.0 optionalDependencies: react: 19.2.3 vue: 3.5.26(typescript@5.6.3) - shiki@3.20.0: + shiki@3.23.0: dependencies: - '@shikijs/core': 3.20.0 - '@shikijs/engine-javascript': 3.20.0 - '@shikijs/engine-oniguruma': 3.20.0 - '@shikijs/langs': 3.20.0 - '@shikijs/themes': 3.20.0 - '@shikijs/types': 3.20.0 + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -9102,7 +9174,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.3.8(react@19.2.3): + swr@2.4.1(react@19.2.3): dependencies: dequal: 2.0.3 react: 19.2.3 @@ -9164,7 +9236,7 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.0.2: {} + tinyexec@1.1.1: {} tinyglobby@0.2.15: dependencies: @@ -9222,7 +9294,7 @@ snapshots: typescript@5.6.3: {} - ufo@1.6.1: {} + ufo@1.6.3: {} undici-types@6.21.0: {} @@ -9258,7 +9330,7 @@ snapshots: unist-util-remove-position@5.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 unist-util-stringify-position@4.0.0: dependencies: @@ -9269,7 +9341,7 @@ snapshots: '@types/unist': 3.0.3 unist-util-is: 6.0.1 - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 unist-util-is: 6.0.1 @@ -9421,8 +9493,6 @@ snapshots: dependencies: vscode-languageserver-protocol: 3.17.5 - vscode-uri@3.0.8: {} - vscode-uri@3.1.0: {} vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.26(typescript@5.6.3)): diff --git a/frontend/src/api/admin/accounts.ts b/frontend/src/api/admin/accounts.ts index 8e40e18b33db6a339e82ecfdd49024d45b7dabf2..a146f1f708c700f0dfe7934e994536fec0ef7eff 100644 --- a/frontend/src/api/admin/accounts.ts +++ b/frontend/src/api/admin/accounts.ts @@ -38,6 +38,8 @@ export async function list( search?: string privacy_mode?: string lite?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal @@ -71,6 +73,8 @@ export async function listWithEtag( search?: string privacy_mode?: string lite?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal @@ -500,7 +504,11 @@ export async function exportData(options?: { platform?: string type?: string status?: string + group?: string + privacy_mode?: string search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' } includeProxies?: boolean }): Promise { @@ -508,11 +516,15 @@ export async function exportData(options?: { if (options?.ids && options.ids.length > 0) { params.ids = options.ids.join(',') } else if (options?.filters) { - const { platform, type, status, search } = options.filters + const { platform, type, status, group, privacy_mode, search, sort_by, sort_order } = options.filters if (platform) params.platform = platform if (type) params.type = type if (status) params.status = status + if (group) params.group = group + if (privacy_mode) params.privacy_mode = privacy_mode if (search) params.search = search + if (sort_by) params.sort_by = sort_by + if (sort_order) params.sort_order = sort_order } if (options?.includeProxies === false) { params.include_proxies = 'false' diff --git a/frontend/src/api/admin/announcements.ts b/frontend/src/api/admin/announcements.ts index d02fdda7720b6c56675f9f37b058848f37a97403..92392a675348c2856588fdbb13f98f414e4de952 100644 --- a/frontend/src/api/admin/announcements.ts +++ b/frontend/src/api/admin/announcements.ts @@ -17,10 +17,16 @@ export async function list( filters?: { status?: string search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' + }, + options?: { + signal?: AbortSignal } ): Promise> { const { data } = await apiClient.get>('/admin/announcements', { - params: { page, page_size: pageSize, ...filters } + params: { page, page_size: pageSize, ...filters }, + signal: options?.signal }) return data } @@ -49,11 +55,21 @@ export async function getReadStatus( id: number, page: number = 1, pageSize: number = 20, - search: string = '' + filters?: { + search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' + }, + options?: { + signal?: AbortSignal + } ): Promise> { const { data } = await apiClient.get>( `/admin/announcements/${id}/read-status`, - { params: { page, page_size: pageSize, search } } + { + params: { page, page_size: pageSize, ...filters }, + signal: options?.signal + } ) return data } @@ -68,4 +84,3 @@ const announcementsAPI = { } export default announcementsAPI - diff --git a/frontend/src/api/admin/channels.ts b/frontend/src/api/admin/channels.ts index 5334dd473d9fac4d2540371c0ce78b5749e741e8..b34550222b832bf1a3c9eb28418e082fc363c563 100644 --- a/frontend/src/api/admin/channels.ts +++ b/frontend/src/api/admin/channels.ts @@ -83,6 +83,8 @@ export async function list( filters?: { status?: string search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal } ): Promise> { diff --git a/frontend/src/api/admin/groups.ts b/frontend/src/api/admin/groups.ts index 5885dc6ad7e9c0265610702a79121f2eca9bc1a3..8739d5cbe9a8b74f4e0af1f61ec8b30aa33fb21f 100644 --- a/frontend/src/api/admin/groups.ts +++ b/frontend/src/api/admin/groups.ts @@ -27,6 +27,8 @@ export async function list( status?: 'active' | 'inactive' is_exclusive?: boolean search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal diff --git a/frontend/src/api/admin/index.ts b/frontend/src/api/admin/index.ts index da1e3cfa8f90f386f029e6877ffd2f379e7f1ee6..72597365434ff09687adc59155464e103fc0221f 100644 --- a/frontend/src/api/admin/index.ts +++ b/frontend/src/api/admin/index.ts @@ -26,6 +26,7 @@ import scheduledTestsAPI from './scheduledTests' import backupAPI from './backup' import tlsFingerprintProfileAPI from './tlsFingerprintProfile' import channelsAPI from './channels' +import adminPaymentAPI from './payment' /** * Unified admin API object for convenient access @@ -53,7 +54,8 @@ export const adminAPI = { scheduledTests: scheduledTestsAPI, backup: backupAPI, tlsFingerprintProfiles: tlsFingerprintProfileAPI, - channels: channelsAPI + channels: channelsAPI, + payment: adminPaymentAPI } export { @@ -79,7 +81,8 @@ export { scheduledTestsAPI, backupAPI, tlsFingerprintProfileAPI, - channelsAPI + channelsAPI, + adminPaymentAPI } export default adminAPI diff --git a/frontend/src/api/admin/payment.ts b/frontend/src/api/admin/payment.ts new file mode 100644 index 0000000000000000000000000000000000000000..946c91b07e784cb1f0c4f544bda53a8b0fc04897 --- /dev/null +++ b/frontend/src/api/admin/payment.ts @@ -0,0 +1,176 @@ +/** + * Admin Payment API endpoints + * Handles payment management operations for administrators + */ + +import { apiClient } from '../client' +import type { + DashboardStats, + PaymentOrder, + PaymentChannel, + SubscriptionPlan, + ProviderInstance +} from '@/types/payment' +import type { BasePaginationResponse } from '@/types' + +/** Admin-facing payment config returned by GET /admin/payment/config */ +export interface AdminPaymentConfig { + enabled: boolean + min_amount: number + max_amount: number + daily_limit: number + order_timeout_minutes: number + max_pending_orders: number + enabled_payment_types: string[] + balance_disabled: boolean + load_balance_strategy: string + product_name_prefix: string + product_name_suffix: string + help_image_url: string + help_text: string +} + +/** Fields accepted by PUT /admin/payment/config (all optional via pointer semantics) */ +export interface UpdatePaymentConfigRequest { + enabled?: boolean + min_amount?: number + max_amount?: number + daily_limit?: number + order_timeout_minutes?: number + max_pending_orders?: number + enabled_payment_types?: string[] + balance_disabled?: boolean + load_balance_strategy?: string + product_name_prefix?: string + product_name_suffix?: string + help_image_url?: string + help_text?: string +} + +export const adminPaymentAPI = { + // ==================== Config ==================== + + /** Get payment configuration (admin view) */ + getConfig() { + return apiClient.get('/admin/payment/config') + }, + + /** Update payment configuration */ + updateConfig(data: UpdatePaymentConfigRequest) { + return apiClient.put('/admin/payment/config', data) + }, + + // ==================== Dashboard ==================== + + /** Get payment dashboard statistics */ + getDashboard(days?: number) { + return apiClient.get('/admin/payment/dashboard', { + params: days ? { days } : undefined + }) + }, + + // ==================== Orders ==================== + + /** Get all orders (paginated, with filters) */ + getOrders(params?: { + page?: number + page_size?: number + status?: string + payment_type?: string + user_id?: number + keyword?: string + start_date?: string + end_date?: string + order_type?: string + }) { + return apiClient.get>('/admin/payment/orders', { params }) + }, + + /** Get a specific order by ID */ + getOrder(id: number) { + return apiClient.get(`/admin/payment/orders/${id}`) + }, + + /** Cancel an order (admin) */ + cancelOrder(id: number) { + return apiClient.post(`/admin/payment/orders/${id}/cancel`) + }, + + /** Retry recharge for a failed order */ + retryRecharge(id: number) { + return apiClient.post(`/admin/payment/orders/${id}/retry`) + }, + + /** Process a refund */ + refundOrder(id: number, data: { amount: number; reason: string; deduct_balance?: boolean; force?: boolean }) { + return apiClient.post(`/admin/payment/orders/${id}/refund`, data) + }, + + // ==================== Channels ==================== + + /** Get all payment channels */ + getChannels() { + return apiClient.get('/admin/payment/channels') + }, + + /** Create a payment channel */ + createChannel(data: Partial) { + return apiClient.post('/admin/payment/channels', data) + }, + + /** Update a payment channel */ + updateChannel(id: number, data: Partial) { + return apiClient.put(`/admin/payment/channels/${id}`, data) + }, + + /** Delete a payment channel */ + deleteChannel(id: number) { + return apiClient.delete(`/admin/payment/channels/${id}`) + }, + + // ==================== Subscription Plans ==================== + + /** Get all subscription plans */ + getPlans() { + return apiClient.get('/admin/payment/plans') + }, + + /** Create a subscription plan */ + createPlan(data: Record) { + return apiClient.post('/admin/payment/plans', data) + }, + + /** Update a subscription plan */ + updatePlan(id: number, data: Record) { + return apiClient.put(`/admin/payment/plans/${id}`, data) + }, + + /** Delete a subscription plan */ + deletePlan(id: number) { + return apiClient.delete(`/admin/payment/plans/${id}`) + }, + + // ==================== Provider Instances ==================== + + /** Get all provider instances */ + getProviders() { + return apiClient.get('/admin/payment/providers') + }, + + /** Create a provider instance */ + createProvider(data: Partial) { + return apiClient.post('/admin/payment/providers', data) + }, + + /** Update a provider instance */ + updateProvider(id: number, data: Partial) { + return apiClient.put(`/admin/payment/providers/${id}`, data) + }, + + /** Delete a provider instance */ + deleteProvider(id: number) { + return apiClient.delete(`/admin/payment/providers/${id}`) + } +} + +export default adminPaymentAPI diff --git a/frontend/src/api/admin/promo.ts b/frontend/src/api/admin/promo.ts index 6a8c4559e939766b8b7cd435cc51708f7b03f0ca..b24dffc2003dc0a2cd441119c551fbd173a0fa6e 100644 --- a/frontend/src/api/admin/promo.ts +++ b/frontend/src/api/admin/promo.ts @@ -17,10 +17,16 @@ export async function list( filters?: { status?: string search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' + }, + options?: { + signal?: AbortSignal } ): Promise> { const { data } = await apiClient.get>('/admin/promo-codes', { - params: { page, page_size: pageSize, ...filters } + params: { page, page_size: pageSize, ...filters }, + signal: options?.signal }) return data } diff --git a/frontend/src/api/admin/proxies.ts b/frontend/src/api/admin/proxies.ts index 5e31ae20f75a29b302783026975661eee899b1fc..3e041ba9a5d134541fe86ed557e1c63f57cd5f5b 100644 --- a/frontend/src/api/admin/proxies.ts +++ b/frontend/src/api/admin/proxies.ts @@ -29,6 +29,8 @@ export async function list( protocol?: string status?: 'active' | 'inactive' search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal @@ -227,16 +229,20 @@ export async function exportData(options?: { protocol?: string status?: 'active' | 'inactive' search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' } }): Promise { const params: Record = {} if (options?.ids && options.ids.length > 0) { params.ids = options.ids.join(',') } else if (options?.filters) { - const { protocol, status, search } = options.filters + const { protocol, status, search, sort_by, sort_order } = options.filters if (protocol) params.protocol = protocol if (status) params.status = status if (search) params.search = search + if (sort_by) params.sort_by = sort_by + if (sort_order) params.sort_order = sort_order } const { data } = await apiClient.get('/admin/proxies/data', { params }) return data diff --git a/frontend/src/api/admin/redeem.ts b/frontend/src/api/admin/redeem.ts index a53c3566bbf261bda3eb3cabc4fe02e4deb0375a..57626b1efd9fe0d655cd60b54e0356d86198d2fc 100644 --- a/frontend/src/api/admin/redeem.ts +++ b/frontend/src/api/admin/redeem.ts @@ -25,6 +25,8 @@ export async function list( type?: RedeemCodeType status?: 'active' | 'used' | 'expired' | 'unused' search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal @@ -151,7 +153,10 @@ export async function getStats(): Promise<{ */ export async function exportCodes(filters?: { type?: RedeemCodeType - status?: 'active' | 'used' | 'expired' + status?: 'used' | 'expired' | 'unused' + search?: string + sort_by?: string + sort_order?: 'asc' | 'desc' }): Promise { const response = await apiClient.get('/admin/redeem-codes/export', { params: filters, diff --git a/frontend/src/api/admin/settings.ts b/frontend/src/api/admin/settings.ts index b7ee6be5cebc6533088cfa62486450931714567c..504abe9c9b07fa79828a70558292f8201189a634 100644 --- a/frontend/src/api/admin/settings.ts +++ b/frontend/src/api/admin/settings.ts @@ -38,8 +38,8 @@ export interface SystemSettings { doc_url: string home_content: string hide_ccs_import_button: boolean - purchase_subscription_enabled: boolean - purchase_subscription_url: string + table_default_page_size: number + table_page_size_options: number[] backend_mode_enabled: boolean custom_menu_items: CustomMenuItem[] custom_endpoints: CustomEndpoint[] @@ -62,6 +62,30 @@ export interface SystemSettings { linuxdo_connect_client_secret_configured: boolean linuxdo_connect_redirect_url: string + // Generic OIDC OAuth settings + oidc_connect_enabled: boolean + oidc_connect_provider_name: string + oidc_connect_client_id: string + oidc_connect_client_secret_configured: boolean + oidc_connect_issuer_url: string + oidc_connect_discovery_url: string + oidc_connect_authorize_url: string + oidc_connect_token_url: string + oidc_connect_userinfo_url: string + oidc_connect_jwks_url: string + oidc_connect_scopes: string + oidc_connect_redirect_url: string + oidc_connect_frontend_redirect_url: string + oidc_connect_token_auth_method: string + oidc_connect_use_pkce: boolean + oidc_connect_validate_id_token: boolean + oidc_connect_allowed_signing_algs: string + oidc_connect_clock_skew_seconds: number + oidc_connect_require_email_verified: boolean + oidc_connect_userinfo_email_path: string + oidc_connect_userinfo_id_path: string + oidc_connect_userinfo_username_path: string + // Model fallback configuration enable_model_fallback: boolean fallback_model_anthropic: string @@ -90,6 +114,26 @@ export interface SystemSettings { enable_fingerprint_unification: boolean enable_metadata_passthrough: boolean enable_cch_signing: boolean + + // Payment configuration + payment_enabled: boolean + payment_min_amount: number + payment_max_amount: number + payment_daily_limit: number + payment_order_timeout_minutes: number + payment_max_pending_orders: number + payment_enabled_types: string[] + payment_balance_disabled: boolean + payment_load_balance_strategy: string + payment_product_name_prefix: string + payment_product_name_suffix: string + payment_help_image_url: string + payment_help_text: string + payment_cancel_rate_limit_enabled: boolean + payment_cancel_rate_limit_max: number + payment_cancel_rate_limit_window: number + payment_cancel_rate_limit_unit: string + payment_cancel_rate_limit_window_mode: string } export interface UpdateSettingsRequest { @@ -112,8 +156,8 @@ export interface UpdateSettingsRequest { doc_url?: string home_content?: string hide_ccs_import_button?: boolean - purchase_subscription_enabled?: boolean - purchase_subscription_url?: string + table_default_page_size?: number + table_page_size_options?: number[] backend_mode_enabled?: boolean custom_menu_items?: CustomMenuItem[] custom_endpoints?: CustomEndpoint[] @@ -131,6 +175,28 @@ export interface UpdateSettingsRequest { linuxdo_connect_client_id?: string linuxdo_connect_client_secret?: string linuxdo_connect_redirect_url?: string + oidc_connect_enabled?: boolean + oidc_connect_provider_name?: string + oidc_connect_client_id?: string + oidc_connect_client_secret?: string + oidc_connect_issuer_url?: string + oidc_connect_discovery_url?: string + oidc_connect_authorize_url?: string + oidc_connect_token_url?: string + oidc_connect_userinfo_url?: string + oidc_connect_jwks_url?: string + oidc_connect_scopes?: string + oidc_connect_redirect_url?: string + oidc_connect_frontend_redirect_url?: string + oidc_connect_token_auth_method?: string + oidc_connect_use_pkce?: boolean + oidc_connect_validate_id_token?: boolean + oidc_connect_allowed_signing_algs?: string + oidc_connect_clock_skew_seconds?: number + oidc_connect_require_email_verified?: boolean + oidc_connect_userinfo_email_path?: string + oidc_connect_userinfo_id_path?: string + oidc_connect_userinfo_username_path?: string enable_model_fallback?: boolean fallback_model_anthropic?: string fallback_model_openai?: string @@ -148,6 +214,25 @@ export interface UpdateSettingsRequest { enable_fingerprint_unification?: boolean enable_metadata_passthrough?: boolean enable_cch_signing?: boolean + // Payment configuration + payment_enabled?: boolean + payment_min_amount?: number + payment_max_amount?: number + payment_daily_limit?: number + payment_order_timeout_minutes?: number + payment_max_pending_orders?: number + payment_enabled_types?: string[] + payment_balance_disabled?: boolean + payment_load_balance_strategy?: string + payment_product_name_prefix?: string + payment_product_name_suffix?: string + payment_help_image_url?: string + payment_help_text?: string + payment_cancel_rate_limit_enabled?: boolean + payment_cancel_rate_limit_max?: number + payment_cancel_rate_limit_window?: number + payment_cancel_rate_limit_unit?: string + payment_cancel_rate_limit_window_mode?: string } /** diff --git a/frontend/src/api/admin/usage.ts b/frontend/src/api/admin/usage.ts index d21b28dcdb830714c63f92fcf5f396fc4e77e6a0..37df7553ff29f08e9edf2dd4b6fb8a7b7e48b659 100644 --- a/frontend/src/api/admin/usage.ts +++ b/frontend/src/api/admin/usage.ts @@ -81,6 +81,8 @@ export interface AdminUsageQueryParams extends UsageQueryParams { user_id?: number exact_total?: boolean billing_mode?: string + sort_by?: string + sort_order?: 'asc' | 'desc' } // ==================== API Functions ==================== diff --git a/frontend/src/api/admin/users.ts b/frontend/src/api/admin/users.ts index bbf0ab512015d070ef8467cfb8733c80d3ac8c4f..39cb1dfa69217d7492e592d955cc5d7f2eb2aa63 100644 --- a/frontend/src/api/admin/users.ts +++ b/frontend/src/api/admin/users.ts @@ -24,6 +24,8 @@ export async function list( group_name?: string // fuzzy filter by allowed group name attributes?: Record // attributeId -> value include_subscriptions?: boolean + sort_by?: string + sort_order?: 'asc' | 'desc' }, options?: { signal?: AbortSignal @@ -37,7 +39,9 @@ export async function list( role: filters?.role, search: filters?.search, group_name: filters?.group_name, - include_subscriptions: filters?.include_subscriptions + include_subscriptions: filters?.include_subscriptions, + sort_by: filters?.sort_by, + sort_order: filters?.sort_order } // Add attribute filters as attr[id]=value diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index c5e1f35daf18a327bd3c1d838fc5d5a8aca8a288..837c4f4cf7068fe1ce394f49ad83131d346cff01 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -357,6 +357,28 @@ export async function completeLinuxDoOAuthRegistration( return data } +/** + * Complete OIDC OAuth registration by supplying an invitation code + * @param pendingOAuthToken - Short-lived JWT from the OAuth callback + * @param invitationCode - Invitation code entered by the user + * @returns Token pair on success + */ +export async function completeOIDCOAuthRegistration( + pendingOAuthToken: string, + invitationCode: string +): Promise<{ access_token: string; refresh_token: string; expires_in: number; token_type: string }> { + const { data } = await apiClient.post<{ + access_token: string + refresh_token: string + expires_in: number + token_type: string + }>('/auth/oauth/oidc/complete-registration', { + pending_oauth_token: pendingOAuthToken, + invitation_code: invitationCode + }) + return data +} + export const authAPI = { login, login2FA, @@ -380,7 +402,8 @@ export const authAPI = { resetPassword, refreshToken, revokeAllSessions, - completeLinuxDoOAuthRegistration + completeLinuxDoOAuthRegistration, + completeOIDCOAuthRegistration } export default authAPI diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 95f9ff31802a5b8a47d69f4edb8c241e80b930ba..2908c6b134833e898b17ae306564749e939d2ee9 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -92,10 +92,13 @@ apiClient.interceptors.response.use( response.data = apiResponse.data } else { // API error + const resp = apiResponse as unknown as Record return Promise.reject({ status: response.status, code: apiResponse.code, - message: apiResponse.message || 'Unknown error' + message: apiResponse.message || 'Unknown error', + reason: resp.reason, + metadata: resp.metadata, }) } } @@ -268,7 +271,9 @@ apiClient.interceptors.response.use( status, code: apiData.code, error: apiData.error, - message: apiData.message || apiData.detail || error.message + message: apiData.message || apiData.detail || error.message, + reason: apiData.reason, + metadata: apiData.metadata, }) } diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a6502a8f02be76ac63c35ba944faf696cbcb1ef5..3b38eaa59762050b73bb01b3289771fbdb0c650c 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -14,6 +14,7 @@ export { keysAPI } from './keys' export { usageAPI } from './usage' export { userAPI } from './user' export { redeemAPI, type RedeemHistoryItem } from './redeem' +export { paymentAPI } from './payment' export { userGroupsAPI } from './groups' export { totpAPI } from './totp' export { default as announcementsAPI } from './announcements' diff --git a/frontend/src/api/keys.ts b/frontend/src/api/keys.ts index 137e10bafc99296e514f1b068cc78c5b2b16c1e9..34dd5b4b76ae0bd5db636ca4cbcb2f8152173f69 100644 --- a/frontend/src/api/keys.ts +++ b/frontend/src/api/keys.ts @@ -17,7 +17,13 @@ import type { ApiKey, CreateApiKeyRequest, UpdateApiKeyRequest, PaginatedRespons export async function list( page: number = 1, pageSize: number = 10, - filters?: { search?: string; status?: string; group_id?: number | string }, + filters?: { + search?: string + status?: string + group_id?: number | string + sort_by?: string + sort_order?: 'asc' | 'desc' + }, options?: { signal?: AbortSignal } diff --git a/frontend/src/api/payment.ts b/frontend/src/api/payment.ts new file mode 100644 index 0000000000000000000000000000000000000000..1389b60f88d386372f178a844c85d3dffad44f7a --- /dev/null +++ b/frontend/src/api/payment.ts @@ -0,0 +1,79 @@ +/** + * User Payment API endpoints + * Handles payment operations for regular users + */ + +import { apiClient } from './client' +import type { + PaymentConfig, + SubscriptionPlan, + PaymentChannel, + MethodLimitsResponse, + CheckoutInfoResponse, + CreateOrderRequest, + CreateOrderResult, + PaymentOrder +} from '@/types/payment' +import type { BasePaginationResponse } from '@/types' + +export const paymentAPI = { + /** Get payment configuration (enabled types, limits, etc.) */ + getConfig() { + return apiClient.get('/payment/config') + }, + + /** Get available subscription plans */ + getPlans() { + return apiClient.get('/payment/plans') + }, + + /** Get available payment channels */ + getChannels() { + return apiClient.get('/payment/channels') + }, + + /** Get all checkout page data in a single call */ + getCheckoutInfo() { + return apiClient.get('/payment/checkout-info') + }, + + /** Get payment method limits and fee rates */ + getLimits() { + return apiClient.get('/payment/limits') + }, + + /** Create a new payment order */ + createOrder(data: CreateOrderRequest) { + return apiClient.post('/payment/orders', data) + }, + + /** Get current user's orders */ + getMyOrders(params?: { page?: number; page_size?: number; status?: string }) { + return apiClient.get>('/payment/orders/my', { params }) + }, + + /** Get a specific order by ID */ + getOrder(id: number) { + return apiClient.get(`/payment/orders/${id}`) + }, + + /** Cancel a pending order */ + cancelOrder(id: number) { + return apiClient.post(`/payment/orders/${id}/cancel`) + }, + + /** Verify order payment status with upstream provider */ + verifyOrder(outTradeNo: string) { + return apiClient.post('/payment/orders/verify', { out_trade_no: outTradeNo }) + }, + + /** Verify order payment status without auth (public endpoint for result page) */ + verifyOrderPublic(outTradeNo: string) { + return apiClient.post('/payment/public/orders/verify', { out_trade_no: outTradeNo }) + }, + + /** Request a refund for a completed order */ + requestRefund(id: number, data: { reason: string }) { + return apiClient.post(`/payment/orders/${id}/refund-request`, data) + } +} diff --git a/frontend/src/api/usage.ts b/frontend/src/api/usage.ts index 6efd7657fc4fc84ff1866cf488eb0f3b021682d2..802c428f80b9a8c43f44121161fdb90f40714461 100644 --- a/frontend/src/api/usage.ts +++ b/frontend/src/api/usage.ts @@ -91,7 +91,7 @@ export async function list( * @returns Paginated list of usage logs */ export async function query( - params: UsageQueryParams, + params: UsageQueryParams & { sort_by?: string; sort_order?: 'asc' | 'desc' }, config: { signal?: AbortSignal } = {} ): Promise> { const { data } = await apiClient.get>('/usage', { diff --git a/frontend/src/assets/icons/alipay.svg b/frontend/src/assets/icons/alipay.svg new file mode 100644 index 0000000000000000000000000000000000000000..22c744c34b07bc3332cb8cc2ebccc76f148211bd --- /dev/null +++ b/frontend/src/assets/icons/alipay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/easypay.svg b/frontend/src/assets/icons/easypay.svg new file mode 100644 index 0000000000000000000000000000000000000000..7c7df9a8fa179f4f49dd7aa6e5c8c5bd2dbd1d2b --- /dev/null +++ b/frontend/src/assets/icons/easypay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/stripe.svg b/frontend/src/assets/icons/stripe.svg new file mode 100644 index 0000000000000000000000000000000000000000..7bfb7250e9752c270a8f58a6a6d59b57de30772c --- /dev/null +++ b/frontend/src/assets/icons/stripe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icons/wxpay.svg b/frontend/src/assets/icons/wxpay.svg new file mode 100644 index 0000000000000000000000000000000000000000..3e102acf96104218c8399a9a6a19275e2bbf3338 --- /dev/null +++ b/frontend/src/assets/icons/wxpay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/admin/account/AccountTableFilters.vue b/frontend/src/components/admin/account/AccountTableFilters.vue index 6b4741830f5ff8c04cfa6a2b6b6a446be601a9a0..b33dad84e0e727b092a7f215e046b4db4d66c861 100644 --- a/frontend/src/components/admin/account/AccountTableFilters.vue +++ b/frontend/src/components/admin/account/AccountTableFilters.vue @@ -27,7 +27,7 @@ const updatePrivacyMode = (value: string | number | boolean | null) => { emit('u const updateGroup = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, group: value }) } const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }]) const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }, { value: 'bedrock', label: 'AWS Bedrock' }]) -const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }]) +const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }, { value: 'unschedulable', label: t('admin.accounts.status.unschedulable') }]) const privacyOpts = computed(() => [ { value: '', label: t('admin.accounts.allPrivacyModes') }, { value: '__unset__', label: t('admin.accounts.privacyUnset') }, diff --git a/frontend/src/components/admin/account/__tests__/AccountTableFilters.spec.ts b/frontend/src/components/admin/account/__tests__/AccountTableFilters.spec.ts deleted file mode 100644 index 5a0044e5f2a1cdc6d2805a2e69cc0029c06eafb7..0000000000000000000000000000000000000000 --- a/frontend/src/components/admin/account/__tests__/AccountTableFilters.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { mount } from '@vue/test-utils' - -import AccountTableFilters from '../AccountTableFilters.vue' - -vi.mock('vue-i18n', async () => { - const actual = await vi.importActual('vue-i18n') - return { - ...actual, - useI18n: () => ({ - t: (key: string) => key - }) - } -}) - -describe('AccountTableFilters', () => { - it('renders privacy mode options and emits privacy_mode updates', async () => { - const wrapper = mount(AccountTableFilters, { - props: { - searchQuery: '', - filters: { - platform: '', - type: '', - status: '', - group: '', - privacy_mode: '' - }, - groups: [] - }, - global: { - stubs: { - SearchInput: { - template: '
' - }, - Select: { - props: ['modelValue', 'options'], - emits: ['update:modelValue', 'change'], - template: '
' - } - } - } - }) - - const selects = wrapper.findAll('.select-stub') - expect(selects).toHaveLength(5) - - const privacyOptions = JSON.parse(selects[3].attributes('data-options')) - expect(privacyOptions).toEqual([ - { value: '', label: 'admin.accounts.allPrivacyModes' }, - { value: '__unset__', label: 'admin.accounts.privacyUnset' }, - { value: 'training_off', label: 'Privacy' }, - { value: 'training_set_cf_blocked', label: 'CF' }, - { value: 'training_set_failed', label: 'Fail' } - ]) - }) -}) diff --git a/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue b/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue index a0d9de3ca8fd699bb5a403e55b3eb0842c8f4217..60c01c6dba9b917854fb6dd52ef4b5b87c37645e 100644 --- a/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue +++ b/frontend/src/components/admin/announcements/AnnouncementReadStatusDialog.vue @@ -21,7 +21,15 @@
- + @@ -62,7 +70,7 @@ diff --git a/frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts b/frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..26c87d73dcec8558371c61b44859c17ce1c7060d --- /dev/null +++ b/frontend/src/components/admin/announcements/__tests__/AnnouncementReadStatusDialog.spec.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { flushPromises, mount } from '@vue/test-utils' + +import AnnouncementReadStatusDialog from '../AnnouncementReadStatusDialog.vue' + +const { getReadStatus, showError } = vi.hoisted(() => ({ + getReadStatus: vi.fn(), + showError: vi.fn(), +})) + +vi.mock('@/api/admin', () => ({ + adminAPI: { + announcements: { + getReadStatus, + }, + }, +})) + +vi.mock('@/stores/app', () => ({ + useAppStore: () => ({ + showError, + }), +})) + +vi.mock('vue-i18n', async () => { + const actual = await vi.importActual('vue-i18n') + return { + ...actual, + useI18n: () => ({ + t: (key: string) => key, + }), + } +}) + +vi.mock('@/composables/usePersistedPageSize', () => ({ + getPersistedPageSize: () => 20, +})) + +const BaseDialogStub = { + props: ['show', 'title', 'width'], + emits: ['close'], + template: '
', +} + +describe('AnnouncementReadStatusDialog', () => { + beforeEach(() => { + getReadStatus.mockReset() + showError.mockReset() + vi.useFakeTimers() + }) + + it('closes by aborting active requests and clearing debounced reloads', async () => { + let activeSignal: AbortSignal | undefined + getReadStatus.mockImplementation(async (...args: any[]) => { + activeSignal = args[4]?.signal + return new Promise(() => {}) + }) + + const wrapper = mount(AnnouncementReadStatusDialog, { + props: { + show: false, + announcementId: 1, + }, + global: { + stubs: { + BaseDialog: BaseDialogStub, + DataTable: true, + Pagination: true, + Icon: true, + }, + }, + }) + + await wrapper.setProps({ show: true }) + await flushPromises() + + expect(getReadStatus).toHaveBeenCalledTimes(1) + expect(activeSignal?.aborted).toBe(false) + + const setupState = (wrapper.vm as any).$?.setupState + setupState.search = 'alice' + setupState.handleSearch() + + setupState.handleClose() + await flushPromises() + + expect(activeSignal?.aborted).toBe(true) + expect(wrapper.emitted('close')).toHaveLength(1) + + vi.advanceTimersByTime(350) + await flushPromises() + + expect(getReadStatus).toHaveBeenCalledTimes(1) + }) +}) diff --git a/frontend/src/components/admin/group/GroupRateMultipliersModal.vue b/frontend/src/components/admin/group/GroupRateMultipliersModal.vue index cbd18af6b47d391050f30d0e10579e02a142b397..bf79bea200fae2d91cd1c385260f8eb26a211bdf 100644 --- a/frontend/src/components/admin/group/GroupRateMultipliersModal.vue +++ b/frontend/src/components/admin/group/GroupRateMultipliersModal.vue @@ -196,7 +196,6 @@ :total="localEntries.length" :page="currentPage" :page-size="pageSize" - :page-size-options="[10, 20, 50]" @update:page="currentPage = $event" @update:pageSize="handlePageSizeChange" /> diff --git a/frontend/src/components/admin/payment/AdminOrderDetail.vue b/frontend/src/components/admin/payment/AdminOrderDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..de4b00b5653e3b57688c6c63e9c7d2864db716ef --- /dev/null +++ b/frontend/src/components/admin/payment/AdminOrderDetail.vue @@ -0,0 +1,139 @@ + + + diff --git a/frontend/src/components/admin/payment/AdminOrderTable.vue b/frontend/src/components/admin/payment/AdminOrderTable.vue new file mode 100644 index 0000000000000000000000000000000000000000..0a22930e8d25ae827e24270767959552bae020bf --- /dev/null +++ b/frontend/src/components/admin/payment/AdminOrderTable.vue @@ -0,0 +1,227 @@ + + + diff --git a/frontend/src/components/admin/payment/AdminRefundDialog.vue b/frontend/src/components/admin/payment/AdminRefundDialog.vue new file mode 100644 index 0000000000000000000000000000000000000000..9333828c30a8c050836bae619cde04024a71712c --- /dev/null +++ b/frontend/src/components/admin/payment/AdminRefundDialog.vue @@ -0,0 +1,234 @@ + + + diff --git a/frontend/src/components/admin/payment/DailyRevenueChart.vue b/frontend/src/components/admin/payment/DailyRevenueChart.vue new file mode 100644 index 0000000000000000000000000000000000000000..77583da17b3aaf24a405240211bcb7abfcc6d1f4 --- /dev/null +++ b/frontend/src/components/admin/payment/DailyRevenueChart.vue @@ -0,0 +1,99 @@ + + + diff --git a/frontend/src/components/admin/payment/OrderStatsCards.vue b/frontend/src/components/admin/payment/OrderStatsCards.vue new file mode 100644 index 0000000000000000000000000000000000000000..0f7ec956d9896905f1b42a20fb4a07ccd416b667 --- /dev/null +++ b/frontend/src/components/admin/payment/OrderStatsCards.vue @@ -0,0 +1,77 @@ + + + diff --git a/frontend/src/components/admin/payment/PaymentMethodChart.vue b/frontend/src/components/admin/payment/PaymentMethodChart.vue new file mode 100644 index 0000000000000000000000000000000000000000..dd6909e95bc3488d83be433ba17cf5554a3554c0 --- /dev/null +++ b/frontend/src/components/admin/payment/PaymentMethodChart.vue @@ -0,0 +1,75 @@ + + + diff --git a/frontend/src/components/admin/payment/TopUsersLeaderboard.vue b/frontend/src/components/admin/payment/TopUsersLeaderboard.vue new file mode 100644 index 0000000000000000000000000000000000000000..1c85fcf1613a79d61992147fb42e25433093b78e --- /dev/null +++ b/frontend/src/components/admin/payment/TopUsersLeaderboard.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/components/admin/usage/UsageTable.vue b/frontend/src/components/admin/usage/UsageTable.vue index 9bbdb380b6b7b20879f1e9035027d6c13d448be6..f4494e69c246844a18484e80c3427d56af199e55 100644 --- a/frontend/src/components/admin/usage/UsageTable.vue +++ b/frontend/src/components/admin/usage/UsageTable.vue @@ -1,7 +1,15 @@