80 Commits

Author SHA1 Message Date
masoodafar-web ea7ba3bd3f Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 16m3s
2026-09-24 21:46:12 +03:30
masoodafar-web fe7179f6e9 chore(k8s): deploy staging apps to namespace kbs-stage
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 21m11s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 21:26:15 +03:30
masoodafar-web f79affb44d feat(transactions): enhance transaction queries with user information
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 21m45s
- Added UserFullName and UserId properties to transaction response models for better user context.
- Updated query handlers to populate user information from related entities, ensuring accurate user details in transaction responses.
- Modified Protobuf definitions to include new fields for user identification and display.
- Adjusted mapping profiles to accommodate new user-related fields in transaction data.
2026-09-24 19:28:01 +03:30
masoodafar-web 749cfeb8f4 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 1s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 15:12:58 +03:30
masoodafar-web 6f573c0a29 fix(payment): set TwentyCard package amounts and send rials to gateway
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2s
Gateway 11,200,000 toman and wallet credit 12,656,000 toman; multiply by 10 when posting to TwentyCard API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 15:12:08 +03:30
masoodafar-web 9b82b45e46 chore(staging): point registry and K8S to 185.252.31.44 after server migration
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 50s
2026-09-22 22:18:43 +03:30
masoodafar-web 39bbe59bbc fix(config): resolve appsettings.json merge conflict markers on production
Build and Deploy to Production / build-and-deploy (push) Failing after 1s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 21:21:09 +03:30
masoodafar-web 5c852e499d Merge branch 'kub-stage' into production
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 21:20:39 +03:30
masoodafar-web cbc1225562 fix(payment): update JSON serialization settings and enhance request data structure
- Changed PropertyNamingPolicy in TwentyCardPaymentService from SnakeCaseLower to CamelCase for improved consistency in JSON serialization.
- Added JsonPropertyName attributes for Authority and RedirectUrl properties in TwentyCardRequestData to ensure correct JSON mapping.
2026-09-22 00:59:39 +03:30
masoodafar-web 59ed3857d4 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 7m11s
2026-09-04 07:25:35 +03:30
masoodafar-web c814cd9118 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m2s
2026-08-24 00:10:39 +03:30
masoodafar-web 436e5d7aca Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 9m50s
2026-08-09 02:33:38 +03:30
masoodafar-web 053c5a7c4f Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 17m27s
2026-08-07 14:38:07 +03:30
masoodafar-web 82944e11d0 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-07-26 00:52:02 +03:30
masoodafar-web bb20ad2c4e Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-06-30 02:28:32 +03:30
masoodafar-web 99aab3beff Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 19m40s
2026-06-26 19:30:03 +03:30
masoodafar-web 19be5dc9dc Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 16m51s
2026-06-20 23:45:47 +03:30
m-moghaddam 5c1e3cf601 Merge remote-tracking branch 'origin/kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 19m16s
2026-06-20 17:28:19 +03:30
masoodafar-web d82b7c28a8 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 20m50s
2026-06-19 02:37:34 +03:30
masoodafar-web 3df738e496 Merge branches 'kub-stage' and 'production' of https://git.se.kbs1.ir/admin/CMS into production
Build and Deploy to Production / build-and-deploy (push) Successful in 11m1s
2026-06-08 22:47:48 +03:30
masoodafar-web 3a9c3438e6 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 12m7s
Deploy DiscountOrderService build fix.
2026-06-08 02:30:01 +03:30
masoodafar-web 12ebdc9e87 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
Deploy blog slug auto-generation fix to production.
2026-06-08 02:06:50 +03:30
masoodafar-web 9f1bc2bcdb Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 15m25s
Deploy payment idempotency fixes and per-user payment locks to production.
2026-06-08 02:00:30 +03:30
masoodafar-web 354ba1dd1b Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m37s
2026-05-29 19:59:05 +03:30
masoodafar-web 3b9958a700 fix(ZarinpalReconciliationJob): correct typo in logging method for unverified list
Build and Deploy to Production / build-and-deploy (push) Successful in 19m55s
2026-05-21 20:20:38 +03:30
masoodafar-web 9c562b12c7 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-05-21 20:19:43 +03:30
masoodafar-web 3c9957463d Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 14m52s
2026-05-21 19:27:56 +03:30
masoodafar-web 4df71107a9 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Failing after 12m46s
2026-05-21 18:27:04 +03:30
masoodafar-web a7fc959d3e Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 14m3s
2026-05-18 01:56:29 +03:30
masoodafar-web d96558aa4b Revert "feat(commission): add BulkCreditPayouts — credit wallets for pending payouts"
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
This reverts commit a2d13c72c3.
2026-05-18 00:38:40 +03:30
masoodafar-web a2d13c72c3 feat(commission): add BulkCreditPayouts — credit wallets for pending payouts
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
- commission.proto: add BulkCreditPayouts rpc + request/response messages; bump version to 0.0.198
- BulkCreditPayoutsCommand + Handler: load all Pending payouts for given week, credit UserWallet.Balance for each user, create UserWalletHistory + CommissionPayoutHistory records, set Status=Paid + PaidAt
- CommissionService: wire up BulkCreditPayouts gRPC method
- CommissionProfile: add Mapster mappings for request/result ↔ proto types

Previously ProcessUserPayouts created records with Status=Pending but never credited wallets, making CustomerWithdrawBalance unreachable (it requires Status==Paid).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 00:36:05 +03:30
masoodafar-web 4e8caa6e2c Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 19m44s
2026-05-18 00:12:13 +03:30
masoodafar-web 37a017998c Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 9m24s
2026-05-14 01:13:03 +03:30
masoodafar-web 16acb8f3b5 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 7m7s
2026-05-10 21:37:51 +03:30
masoodafar-web 34d2354fd4 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 9m37s
2026-05-05 01:54:44 +03:30
masoodafar-web be2b22c50a Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 10m25s
2026-05-04 23:55:16 +03:30
masoodafar-web b260fd56fa Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 10m7s
2026-05-04 00:04:36 +03:30
masoodafar-web 1e3ba03c5c Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m34s
2026-05-01 00:56:21 +03:30
masoodafar-web ee7d29015f Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-05-01 00:47:44 +03:30
masoodafar-web b053d86006 feat: Add GetNetworkTree stored procedure for binary network retrieval with activation logic
Build and Deploy to Production / build-and-deploy (push) Successful in 8m27s
2026-05-01 00:27:44 +03:30
masoodafar-web 15044c66cb Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-05-01 00:15:48 +03:30
masoodafar-web 0983cdf88f Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m56s
2026-04-30 04:27:43 +03:30
masoodafar-web b8120dd1b9 config: add master OTP code to production appsettings 2026-04-29 00:49:55 +03:30
masoodafar-web 7ce5539793 feat: add master OTP bypass for login + referral code field support 2026-04-29 00:49:33 +03:30
masoodafar-web 9a4f1e6401 chore: merge kub-stage into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m11s
2026-04-28 23:28:44 +03:30
masoodafar-web 091a04f3aa Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 24m9s
2026-03-15 03:19:41 +03:30
masoodafar-web 62a0f8594d fix: Update Kavenegar API key and modify database connection string
Build and Deploy to Production / build-and-deploy (push) Successful in 8m39s
2026-02-28 05:03:38 +03:30
masoodafar-web fb81aff095 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m48s
# Conflicts:
#	.gitea/workflows/kub-deploy.yml
#	k8s/staging/cms-deployment.yaml
#	src/CMSMicroservice.Application/DiscountShopCQ/Commands/CompleteOrderPayment/CompleteOrderPaymentCommandHandler.cs
#	src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs
#	src/CMSMicroservice.Application/WalletCQ/Commands/VerifyDiscountWalletCharge/VerifyDiscountWalletChargeCommandHandler.cs
#	src/CMSMicroservice.Application/WalletCQ/Commands/VerifyMagicWalletCharge/VerifyMagicWalletChargeCommandHandler.cs
#	src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs
#	src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs
#	src/CMSMicroservice.WebApi/appsettings.Production.json
#	src/CMSMicroservice.WebApi/appsettings.Staging.json
#	src/CMSMicroservice.WebApi/appsettings.json
2026-02-28 04:51:57 +03:30
masoodafar-web 491b8a0d0f fix: remove double ×10 Rial conversion in ZarinPalPaymentService
Build and Deploy to Production / build-and-deploy (push) Successful in 10m10s
Root cause: FrontOffice already converts Toman→Rial (×10) before sending
to CMS. ZarinPalPaymentService was multiplying by 10 AGAIN, causing
amounts to be 10x too large.

Example: user enters 500K Toman → FO sends 5M Rial → CMS did ×10 → 50M
Rial sent to ZarinPal → showed 5M Toman instead of 500K.

Changes:
- ZarinPalPaymentService.InitiatePaymentAsync: remove ×10 (amount already Rial)
- ZarinPalPaymentService.VerifyPaymentWithAmountAsync: remove ×10 + accept Rial
- ZarinPalPaymentService.VerifyResult.Amount: return Rial (no /10 conversion)
- VerifyMagicWalletChargeCommandHandler: remove /10 before calling Verify
- PaymentCallbackController: update comments (amount is Rial)
- Also includes: improved HTTP error logging for non-200 responses
- Also includes: production URL fix (kbs1→kbs2)
2026-02-24 00:49:47 +03:30
masoodafar-web be80136ae8 fix: reduce discount order expiry from 30 to 15 minutes
Build and Deploy to Production / build-and-deploy (push) Successful in 9m8s
2026-02-24 00:24:30 +03:30
masoodafar-web 5e9984f0e5 fix: validate discount wallet balance before applying discount in PlaceOrderCommandHandler 2026-02-24 00:24:30 +03:30
masoodafar-web 1d07ae4f71 fix: add missing UserWalletChangeLog for discount shop purchases and discount wallet charge
Bug: when buying from discount shop, DiscountBalance was deducted from DB
but no UserWalletChangeLog was created — making it invisible in wallet history.
Same issue existed for discount wallet top-up (charge).

Fixed in 3 handlers:
- PlaceOrderCommandHandler (fully paid by discount balance path)
- CompleteOrderPaymentCommandHandler (gateway + discount balance path)
- VerifyDiscountWalletChargeCommandHandler (discount wallet charge)
2026-02-24 00:24:30 +03:30
masoodafar-web 775a9bd75d fix: update .gitignore to include environment-specific appsettings files 2026-02-24 00:18:42 +03:30
masoodafar-web 2620a24597 fix: correct production URLs from kbs1 to kbs2 in cms-config
CmsBaseUrl: cms.kbs1.ir → cms.kbs2.ir
FrontOfficeBaseUrl: kbs1.ir → kbs2.ir

This was causing ZarinPal callback to go to staging instead of production,
resulting in 401 errors (-1 unknown error).
2026-02-24 00:18:24 +03:30
masoodafar-web def3b94d89 fix: update appsettings for production environment — adjust URLs and connection strings
Build and Deploy to Production / build-and-deploy (push) Successful in 9m32s
2026-02-23 23:59:41 +03:30
masoodafar-web 3ebe0f9f48 chore(production): remove appsettings.Staging.json — config comes from K8s Secret
Build and Deploy to Production / build-and-deploy (push) Successful in 12m5s
DO NOT cherry-pick this commit to kub-stage branch
2026-02-23 22:10:53 +03:30
masoodafar-web f4e24f7449 feat: externalize appsettings to K8s Secret — config persists independently of image rebuilds
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
- Add cms-config.yaml (K8s Secret) for staging and production
- Mount appsettings.{Environment}.json from Secret into /app/
- Update CI/CD pipelines to apply Secret before Deployment
- Remove redundant env vars (Kestrel, FileStorage) — now in config file
2026-02-23 21:58:56 +03:30
masoodafar-web 540a2ce4fc fix: production uses namespace default + remove foursat namespace references
Build and Deploy to Production / build-and-deploy (push) Successful in 8m44s
2026-02-23 21:36:16 +03:30
masoodafar-web e57f01965a fix: staging ASPNETCORE_ENVIRONMENT=Staging, remove secretKeyRef (config in appsettings)
Build and Deploy to Production / build-and-deploy (push) Failing after 8m36s
2026-02-23 21:17:34 +03:30
masoodafar-web f97c4b0812 fix: use local registry 194.5.195.53:30080 instead of git.se.kbs1.ir for image pull
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-23 20:58:28 +03:30
masoodafar-web fd32e15192 fix: production ingress — add cms.kbs2.ir, use ingressClassName instead of annotation
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-23 20:42:28 +03:30
masoodafar-web 4456e504d4 feat: add PersistentVolume for CMS uploads + apply manifests in CI/CD
Build and Deploy to Production / build-and-deploy (push) Failing after 8m12s
- Add k8s/staging/ and k8s/production/ deployment manifests
- PVC 20Gi for /app/Uploads (file uploads persist across pod restarts)
- FileStorage__UploadPath env var set to /app/Uploads
- Replicas reduced to 1 (ReadWriteOnce PVC)
- Pipelines now: create namespace → apply manifests → restart/update
2026-02-23 20:12:24 +03:30
masoodafar-web cec1e74f6c chore: prevent appsettings.Production.json from merging — keep ours strategy
Build and Deploy to Production / build-and-deploy (push) Successful in 10m59s
2026-02-22 23:59:36 +03:30
masoodafar-web dd9ac48474 fix: update CMS and FrontOffice base URLs to reflect new domain
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2026-02-22 23:53:56 +03:30
masoodafar-web eb1b249869 Merge kub-stage into production
Build and Deploy to Production / build-and-deploy (push) Successful in 11m49s
- Resolved appsettings.Production.json conflict (new MerchantId, SeedWorkers)
- Removed duplicate u21 migration (kept 155925 from production)
- All features: Magic Wallet, Discount Wallet, StockMovement fix, FullInformation expansion, Wallet user_name, gateway activation
2026-02-22 22:21:36 +03:30
masoodafar-web e8e964f9a0 Add migration for UserWallets and ClubMembershipCycles schema updates
- Added columns to UserWallets: MagicActivatedAt, MagicCompletedAt, MagicTotalCredited, MagicTotalDeposited, and WalletMode.
- Created new table ClubMembershipCycles with relevant fields and constraints.
- Established foreign key relationships with Users and ClubMemberships.
- Added indexes for optimized queries on ClubMembershipCycles.
2026-02-22 19:33:56 +03:30
masoodafar-web 9050c00297 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m56s
2026-02-21 20:37:55 +03:30
masoodafar-web 6048824b33 docs: move README to totalDoc, replace with placeholder 2026-02-18 21:44:39 +03:30
masoodafar-web 62a247f097 merge: kub-stage → production (inventory auto-init + initializer worker)
Build and Deploy to Production / build-and-deploy (push) Successful in 9m2s
2026-02-18 01:01:15 +03:30
masoodafar-web af12f996f1 merge: kub-stage into production — fix prod workflow + add CmsBaseUrl/FrontOfficeBaseUrl
Build and Deploy to Production / build-and-deploy (push) Successful in 8m9s
2026-02-17 01:59:28 +03:30
masoodafar-web 3b0d9eadd3 Fix production workflow: use SERVER_PASSWORD, add docker login, use set image for deploy
Build and Deploy to Production / build-and-deploy (push) Successful in 8m17s
2026-02-17 01:23:53 +03:30
masoodafar-web db07838ecb Remove staging workflow files from production branch to prevent cross-deployment 2026-02-17 01:15:50 +03:30
masoodafar-web 167991890c Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 2m43s
Features merged:
- ZarinPal payment gateway (sandbox mode)
- Discount store improvements (100% discount auto-apply)
- VAT breakdown on checkout
- PaymentStatus in order responses (proto v0.0.179)
- ExpirePendingOrdersService (background job for stale orders)
- DeliveryStatus proper mapping + cancel on failed payment
- Inventory management system
- Various bug fixes

Production config preserved:
- DB: 45.149.79.127 / KBS
- CmsBaseUrl: https://cms.kbs1.ir
- FrontOfficeBaseUrl: https://foursat.kbs1.ir
- Payment gateway: sandbox (will show 'درگاه فعال نمیباشد')
- Production workflow unchanged
2026-02-17 00:53:28 +03:30
masoodafar-web 1a425f0d93 chore: Update production workflow to use Nexus registry (no proxy needed)
Build and Deploy to Production / build-and-deploy (push) Failing after 0s
- Changed Dockerfile base images to use 194.5.195.53:32082 (Nexus)
- Updated workflow to use docker-sshpass image from Nexus
- Removed HTTP_PROXY/HTTPS_PROXY dependencies
- Changed deploy method to SSH instead of kubeconfig
- Registry: 194.5.195.53:30080 (Gitea internal)
- K8S Server: 45.149.79.127 (Production)
2026-01-29 22:04:31 +03:30
masoodafar-web 6f8aefc2ce Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 1m40s
2025-12-29 00:36:51 +03:30
masoodafar-web 2bd5c7db78 Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 1m50s
2025-12-27 07:37:30 +03:30
masoodafar-web 0728ec2b76 feat: add background job configuration for weekly commission calculation
Build and Deploy to Production / build-and-deploy (push) Successful in 3m18s
2025-12-27 01:51:32 +03:30
masoodafar-web 1425fb187b Merge branch 'kub-stage' into production
# Conflicts:
#	src/CMSMicroservice.WebApi/Program.cs
2025-12-27 01:49:11 +03:30
masoodafar-web 81d2b39ee1 fix: update Seq ServerUrl and change Hangfire job timezone to local - Adjusted Seq server URL for production and modified recurring job options to use local timezone 2025-12-27 01:14:31 +03:30
masoodafar-web fce194a195 feat: update connection strings and add production configuration - Modified database connection details and created appsettings.Production.json for production environment
Build and Deploy to Production / build-and-deploy (push) Successful in 4m2s
2025-12-27 00:16:36 +03:30
27 changed files with 827 additions and 158 deletions
-46
View File
@@ -1,46 +0,0 @@
name: Push nuget and docker image Actions Workflow
on:
push:
branches:
- stage_new
jobs:
Deploy:
runs-on: windows
steps:
- name: Checkout
uses: https://git.afrino.co/actions/checkout@v3
- name: Setup dotnet
uses: https://git.afrino.co/actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Remove Package Source
run: dotnet nuget remove source FourSat
continue-on-error: true
- name: Add Package Source
run: dotnet nuget add source --name FourSat --username systemuser --password sZSA7PTiv3pUSQZ https://git.afrino.co/api/packages/FourSat/nuget/index.json --store-password-in-clear-text
- name: Install dependencies
run: dotnet restore ".\src\CMSMicroservice.WebApi\CMSMicroservice.WebApi.csproj"
- name: Build
run: dotnet build ".\src\CMSMicroservice.WebApi\CMSMicroservice.WebApi.csproj" --configuration Release --no-restore
- name: Test
run: dotnet test ".\src\CMSMicroservice.WebApi\CMSMicroservice.WebApi.csproj" --no-restore --verbosity normal
- name: Recycle Apppool
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" recycle apppool /apppool.name:cms.kbs1.ir
shell: powershell
- name: Stop Website
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" stop site /site.name:cms.kbs1.ir
shell: powershell
- name: Publish
run: dotnet publish ".\src\CMSMicroservice.WebApi\CMSMicroservice.WebApi.csproj" -c Release -o publish
- name: Copy Publish To IIS Directory
run: Get-ChildItem -Path "publish\*" | Copy-Item -Destination "E:\kbs1.ir\cms.kbs1.ir\" -Recurse -Force
- name: Start Website
run: |
& "C:\Windows\System32\inetsrv\appcmd.exe" start site /site.name:cms.kbs1.ir
shell: powershell
+10 -10
View File
@@ -6,15 +6,15 @@ on:
- kub-stage - kub-stage
env: env:
REGISTRY: 194.5.195.53:30080 REGISTRY: 185.252.31.44:30080
IMAGE_NAME: admin/cms IMAGE_NAME: admin/cms
K8S_SERVER: 194.5.195.53 K8S_SERVER: 185.252.31.44
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: 194.5.195.53:32082/docker-sshpass:latest image: 185.252.31.44:32082/docker-sshpass:latest
options: --privileged options: --privileged
steps: steps:
- name: Start Docker daemon - name: Start Docker daemon
@@ -22,7 +22,7 @@ jobs:
mkdir -p /etc/docker mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DAEMON' cat > /etc/docker/daemon.json << 'DAEMON'
{ {
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] "insecure-registries": ["185.252.31.44:30080", "185.252.31.44:32500", "185.252.31.44:32082"]
} }
DAEMON DAEMON
echo "🚀 Starting Docker daemon..." echo "🚀 Starting Docker daemon..."
@@ -52,14 +52,14 @@ jobs:
- name: Login to Docker registries - name: Login to Docker registries
run: | run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 185.252.31.44:32082 -u admin --password-stdin
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
- name: Publish Protobuf packages - name: Publish Protobuf packages
run: | run: |
echo "📦 Publishing Protobuf packages..." echo "📦 Publishing Protobuf packages..."
docker run --rm --network host -e CI=true -v $(pwd):/src -w /src \ docker run --rm --network host -e CI=true -v $(pwd):/src -w /src \
194.5.195.53:32082/dotnet/sdk:9.0 sh -c ' 185.252.31.44:32082/dotnet/sdk:9.0 sh -c '
for proj in $(find . -name "*Protobuf*.csproj" -type f); do for proj in $(find . -name "*Protobuf*.csproj" -type f); do
echo "📦 $proj" echo "📦 $proj"
dotnet restore "$proj" --configfile src/NuGet.config dotnet restore "$proj" --configfile src/NuGet.config
@@ -93,11 +93,11 @@ jobs:
# Apply config (Secret) first, then deployment # Apply config (Secret) first, then deployment
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} " sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
kubectl apply -f /tmp/cms-config.yaml && kubectl apply -n kbs-stage -f /tmp/cms-config.yaml &&
kubectl apply -f /tmp/cms-deployment.yaml && kubectl apply -n kbs-stage -f /tmp/cms-deployment.yaml &&
crictl rmi ${REGISTRY}/${IMAGE_NAME}:latest 2>/dev/null || true && crictl rmi ${REGISTRY}/${IMAGE_NAME}:latest 2>/dev/null || true &&
kubectl rollout restart deployment/cms && kubectl -n kbs-stage rollout restart deployment/cms &&
kubectl rollout status deployment/cms --timeout=180s && kubectl -n kbs-stage rollout status deployment/cms --timeout=180s &&
rm -f /tmp/cms-config.yaml /tmp/cms-deployment.yaml rm -f /tmp/cms-config.yaml /tmp/cms-deployment.yaml
" "
echo "✅ Deployed!" echo "✅ Deployed!"
+11 -9
View File
@@ -6,7 +6,7 @@ on:
- production - production
env: env:
REGISTRY: 194.5.195.53:30080 REGISTRY: 185.252.31.44:30080
IMAGE_NAME: admin/cms IMAGE_NAME: admin/cms
K8S_SERVER: 45.149.79.127 K8S_SERVER: 45.149.79.127
@@ -14,7 +14,7 @@ jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: 194.5.195.53:32082/docker-sshpass:latest image: 185.252.31.44:32082/docker-sshpass:latest
options: --privileged options: --privileged
steps: steps:
- name: Start Docker daemon - name: Start Docker daemon
@@ -22,7 +22,7 @@ jobs:
mkdir -p /etc/docker mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DAEMON' cat > /etc/docker/daemon.json << 'DAEMON'
{ {
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"] "insecure-registries": ["185.252.31.44:30080", "185.252.31.44:32500", "185.252.31.44:32082"]
} }
DAEMON DAEMON
echo "🚀 Starting Docker daemon..." echo "🚀 Starting Docker daemon..."
@@ -40,7 +40,7 @@ jobs:
done done
if ! docker info >/dev/null 2>&1; then if ! docker info >/dev/null 2>&1; then
echo "❌ Docker daemon failed to start after 3 minutes" echo "❌ Docker daemon failed to start"
exit 1 exit 1
fi fi
@@ -50,14 +50,14 @@ jobs:
- name: Login to Docker registries - name: Login to Docker registries
run: | run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 185.252.31.44:32082 -u admin --password-stdin
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
- name: Publish Protobuf packages - name: Publish Protobuf packages
run: | run: |
echo "📦 Publishing Protobuf packages..." echo "📦 Publishing Protobuf packages..."
docker run --rm --network host -v $(pwd):/src -w /src \ docker run --rm --network host -v $(pwd):/src -w /src \
194.5.195.53:32082/dotnet/sdk:9.0 sh -c ' 185.252.31.44:32082/dotnet/sdk:9.0 sh -c '
for proj in $(find . -name "*Protobuf*.csproj" -type f); do for proj in $(find . -name "*Protobuf*.csproj" -type f); do
echo "📦 $proj" echo "📦 $proj"
dotnet restore "$proj" --configfile src/NuGet.config dotnet restore "$proj" --configfile src/NuGet.config
@@ -65,7 +65,7 @@ jobs:
dotnet pack "$proj" -c Release --no-build -o "$(dirname $proj)/nupkg" dotnet pack "$proj" -c Release --no-build -o "$(dirname $proj)/nupkg"
for nupkg in $(dirname $proj)/nupkg/*.nupkg; do for nupkg in $(dirname $proj)/nupkg/*.nupkg; do
[ -f "$nupkg" ] && dotnet nuget push "$nupkg" \ [ -f "$nupkg" ] && dotnet nuget push "$nupkg" \
--source "http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json" \ --source "http://185.252.31.44:32081/repository/foursat-nuget-hosted/index.json" \
--api-key "admin:87zH26nbqT" \ --api-key "admin:87zH26nbqT" \
--skip-duplicate --allow-insecure-connections || true --skip-duplicate --allow-insecure-connections || true
done done
@@ -75,8 +75,10 @@ jobs:
- name: Build Docker Image - name: Build Docker Image
run: | run: |
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ DOCKER_BUILDKIT=0 docker build --network host \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod . -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
.
- name: Push to Registry - name: Push to Registry
run: | run: |
+2 -2
View File
@@ -1,4 +1,4 @@
FROM 194.5.195.53:32082/dotnet/sdk:9.0 AS build FROM 185.252.31.44:32082/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
# Copy NuGet config first # Copy NuGet config first
@@ -11,7 +11,7 @@ COPY src/ ./
RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile NuGet.config RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile NuGet.config
RUN dotnet publish "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" -c Release -o /app/publish --no-restore RUN dotnet publish "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" -c Release -o /app/publish --no-restore
FROM 194.5.195.53:32082/dotnet/aspnet:9.0 AS runtime FROM 185.252.31.44:32082/dotnet/aspnet:9.0 AS runtime
WORKDIR /app WORKDIR /app
COPY --from=build /app/publish . COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080 ENV ASPNETCORE_URLS=http://+:8080
+4 -4
View File
@@ -33,12 +33,12 @@ stringData:
"DefaultEmail": "info@kbs2.ir", "DefaultEmail": "info@kbs2.ir",
"Packages": { "Packages": {
"4": { "4": {
"GatewayAmountToman": 9700000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 13000000 "WalletCreditToman": 12656000
}, },
"1": { "1": {
"GatewayAmountToman": 97000000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 130000000 "WalletCreditToman": 12656000
} }
} }
}, },
+1 -1
View File
@@ -35,7 +35,7 @@ spec:
spec: spec:
containers: containers:
- name: cms - name: cms
image: 194.5.195.53:30080/admin/cms:prod image: 185.252.31.44:30080/admin/cms:prod
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8080 - containerPort: 8080
+6 -6
View File
@@ -11,7 +11,7 @@ apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: cms-appsettings name: cms-appsettings
namespace: default namespace: kbs-stage
labels: labels:
app: cms app: cms
environment: staging environment: staging
@@ -34,12 +34,12 @@ stringData:
"DefaultEmail": "info@kbs2.ir", "DefaultEmail": "info@kbs2.ir",
"Packages": { "Packages": {
"4": { "4": {
"GatewayAmountToman": 1000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 13000000 "WalletCreditToman": 12656000
}, },
"1": { "1": {
"GatewayAmountToman": 1000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 130000000 "WalletCreditToman": 12656000
} }
} }
}, },
@@ -53,7 +53,7 @@ stringData:
"JwtAudience": "https://localhost", "JwtAudience": "https://localhost",
"JwtExpiryInDays": 5, "JwtExpiryInDays": 5,
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Data Source=194.5.195.53,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False", "DefaultConnection": "Data Source=mssql-svc.platform.svc.cluster.local,1433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False",
"providerName": "System.Data.SqlClient" "providerName": "System.Data.SqlClient"
}, },
"Otp": { "Otp": {
+11 -6
View File
@@ -4,12 +4,14 @@ apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: cms-uploads-pvc name: cms-uploads-pvc
namespace: default namespace: kbs-stage
labels: labels:
app: cms app: cms
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
storageClassName: migrated-local
volumeName: pv-cms-uploads-migrated
resources: resources:
requests: requests:
storage: 20Gi storage: 20Gi
@@ -20,7 +22,7 @@ apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: cms name: cms
namespace: default namespace: kbs-stage
labels: labels:
app: cms app: cms
spec: spec:
@@ -33,10 +35,12 @@ spec:
labels: labels:
app: cms app: cms
spec: spec:
imagePullSecrets:
- name: gitea-registry-secret
containers: containers:
- name: cms - name: cms
image: 194.5.195.53:30080/admin/cms:latest image: 185.252.31.44:30080/admin/cms:latest
imagePullPolicy: Always imagePullPolicy: IfNotPresent
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: http name: http
@@ -89,7 +93,7 @@ apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: cms-svc name: cms-svc
namespace: default namespace: kbs-stage
spec: spec:
selector: selector:
app: cms app: cms
@@ -107,7 +111,7 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: cms-ingress name: cms-ingress
namespace: default namespace: kbs-stage
annotations: annotations:
kubernetes.io/ingress.class: "nginx" kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod" cert-manager.io/cluster-issuer: "letsencrypt-prod"
@@ -115,6 +119,7 @@ metadata:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec: spec:
ingressClassName: nginx
tls: tls:
- hosts: - hosts:
- cms.se.kbs1.ir - cms.se.kbs1.ir
+340
View File
@@ -0,0 +1,340 @@
/*
Cleanup all side-effects of a test FrontOffice user (register + package + club + wallet + invitations).
Usage:
EXEC dbo.usp_CleanupTestUser @Mobile = N'09037437157', @DryRun = 1;
EXEC dbo.usp_CleanupTestUser @Mobile = N'09037437157';
EXEC dbo.usp_CleanupTestUser @Mobile = N'09...', @ReparentChildren = 1;
EXEC dbo.usp_CleanupTestUser @Mobile = N'09...', @ReversePool = 1, @AbortIfPoolCalculated = 1;
Pool reverse: for each ClubMembershipCycle, subtract Packages.ActivationFee
(fallback: ClubMembership.InitialContribution) from CMS.WeeklyCommissionPools
for matching WeekDefinition × PackageId.
Does NOT delete pool rows (shared). Does NOT claw back commission already paid to OTHER users.
Does NOT touch: Packages master, Contracts master, WeekDefinitions, Roles, catalogs.
*/
CREATE OR ALTER PROCEDURE dbo.usp_CleanupTestUser
@UserId BIGINT = NULL,
@Mobile NVARCHAR(20) = NULL,
@HardDeleteUser BIT = 1,
@ReparentChildren BIT = 0,
@ReversePool BIT = 1,
@AbortIfPoolCalculated BIT = 0,
@DryRun BIT = 0
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET QUOTED_IDENTIFIER ON;
SET ANSI_NULLS ON;
IF @UserId IS NULL AND (@Mobile IS NULL OR LTRIM(RTRIM(@Mobile)) = N'')
BEGIN
RAISERROR(N'Provide @UserId and/or @Mobile.', 16, 1);
RETURN;
END;
DECLARE @MobileNorm NVARCHAR(20) = NULL;
IF @Mobile IS NOT NULL
BEGIN
SET @MobileNorm = LTRIM(RTRIM(@Mobile));
IF LEFT(@MobileNorm, 3) = N'+98' SET @MobileNorm = N'0' + SUBSTRING(@MobileNorm, 4, 20);
IF LEFT(@MobileNorm, 2) = N'98' AND LEN(@MobileNorm) = 12 SET @MobileNorm = N'0' + SUBSTRING(@MobileNorm, 3, 20);
IF LEFT(@MobileNorm, 1) = N'9' AND LEN(@MobileNorm) = 10 SET @MobileNorm = N'0' + @MobileNorm;
END;
IF @UserId IS NULL
BEGIN
SELECT TOP (1) @UserId = Id
FROM CMS.Users
WHERE Mobile = @MobileNorm
OR Mobile = @Mobile
OR REPLACE(Mobile, N'+98', N'0') = @MobileNorm
ORDER BY IsDeleted ASC, Id DESC;
END;
IF @UserId IS NULL
BEGIN
RAISERROR(N'User not found for given @UserId/@Mobile.', 16, 1);
RETURN;
END;
DECLARE @UserMobile NVARCHAR(20);
SELECT @UserMobile = Mobile FROM CMS.Users WHERE Id = @UserId;
IF @UserMobile IS NULL
BEGIN
RAISERROR(N'UserId %I64d not found.', 16, 1, @UserId);
RETURN;
END;
DECLARE @ChildCount INT =
(SELECT COUNT(*) FROM CMS.Users WHERE NetworkParentId = @UserId AND IsDeleted = 0);
IF @ChildCount > 0 AND @ReparentChildren = 0 AND @HardDeleteUser = 1
BEGIN
RAISERROR(N'User has %d active network children. Pass @ReparentChildren=1 or soft-delete only.', 16, 1, @ChildCount);
RETURN;
END;
PRINT N'=== usp_CleanupTestUser ===';
PRINT N'UserId=' + CAST(@UserId AS NVARCHAR(20)) + N' Mobile=' + ISNULL(@UserMobile, N'?')
+ N' DryRun=' + CAST(@DryRun AS NVARCHAR(1))
+ N' ReversePool=' + CAST(@ReversePool AS NVARCHAR(1));
DECLARE @Wallets TABLE (WalletId BIGINT PRIMARY KEY);
INSERT INTO @Wallets (WalletId)
SELECT Id FROM CMS.UserWallets WHERE UserId = @UserId;
DECLARE @Tx TABLE (TransactionId BIGINT PRIMARY KEY);
INSERT INTO @Tx (TransactionId)
SELECT DISTINCT x.TransactionId
FROM (
SELECT TransactionId FROM CMS.UserPackagePurchases WHERE UserId = @UserId AND TransactionId IS NOT NULL
UNION
SELECT TransactionId FROM CMS.PaymentTransactions WHERE UserId = @UserId AND TransactionId IS NOT NULL
UNION
SELECT TransactionId FROM CMS.UserOrders WHERE UserId = @UserId AND TransactionId IS NOT NULL
UNION
SELECT TransactionId FROM CMS.ManualPayments WHERE UserId = @UserId AND TransactionId IS NOT NULL
UNION
SELECT TransactionId FROM CMS.DayaLoanContracts WHERE UserId = @UserId AND TransactionId IS NOT NULL
UNION
SELECT TransactionId FROM CMS.DiscountOrders WHERE UserId = @UserId AND TransactionId IS NOT NULL
UNION
SELECT h.RefrenceId
FROM CMS.UserWalletHistories h
INNER JOIN @Wallets w ON w.WalletId = h.WalletId
WHERE h.RefrenceId IS NOT NULL
) x
WHERE x.TransactionId IS NOT NULL;
DECLARE @Orders TABLE (OrderId BIGINT PRIMARY KEY);
INSERT INTO @Orders (OrderId)
SELECT Id FROM CMS.UserOrders WHERE UserId = @UserId;
DECLARE @DiscountOrders TABLE (DiscountOrderId BIGINT PRIMARY KEY);
INSERT INTO @DiscountOrders (DiscountOrderId)
SELECT Id FROM CMS.DiscountOrders WHERE UserId = @UserId;
DECLARE @ClubMemberships TABLE (ClubMembershipId BIGINT PRIMARY KEY);
INSERT INTO @ClubMemberships (ClubMembershipId)
SELECT Id FROM CMS.ClubMemberships WHERE UserId = @UserId;
DECLARE @Cycles TABLE (CycleId BIGINT PRIMARY KEY);
INSERT INTO @Cycles (CycleId)
SELECT Id FROM CMS.ClubMembershipCycles WHERE UserId = @UserId
UNION
SELECT Id FROM CMS.ClubMembershipCycles WHERE ClubMembershipId IN (SELECT ClubMembershipId FROM @ClubMemberships);
DECLARE @Payouts TABLE (PayoutId BIGINT PRIMARY KEY);
INSERT INTO @Payouts (PayoutId)
SELECT Id FROM CMS.UserCommissionPayouts WHERE UserId = @UserId;
DECLARE @PoolReversals TABLE (
CycleId BIGINT,
PackageId BIGINT,
WeekDefinitionId BIGINT NULL,
PoolId BIGINT NULL,
Fee BIGINT,
PoolAmountBefore BIGINT NULL,
IsCalculated BIT NULL,
Note NVARCHAR(200)
);
INSERT INTO @PoolReversals (CycleId, PackageId, WeekDefinitionId, PoolId, Fee, PoolAmountBefore, IsCalculated, Note)
SELECT
c.Id,
c.PackageId,
wd.Id,
wp.Id,
ISNULL(NULLIF(cm.InitialContribution, 0), ISNULL(p.ActivationFee, 0)),
wp.TotalPoolAmount,
wp.IsCalculated,
CASE
WHEN wd.Id IS NULL THEN N'NO_WEEK_MATCH'
WHEN wp.Id IS NULL THEN N'NO_POOL_ROW'
WHEN wp.IsCalculated = 1 THEN N'POOL_ALREADY_CALCULATED'
ELSE N'OK'
END
FROM CMS.ClubMembershipCycles c
LEFT JOIN CMS.ClubMemberships cm ON cm.Id = c.ClubMembershipId
LEFT JOIN CMS.Packages p ON p.Id = c.PackageId
LEFT JOIN CMS.WeekDefinitions wd
ON CAST(c.PackagePurchasedAt AS DATE) >= CAST(wd.StartDate AS DATE)
AND CAST(c.PackagePurchasedAt AS DATE) <= CAST(wd.EndDate AS DATE)
AND wd.IsDeleted = 0
LEFT JOIN CMS.WeeklyCommissionPools wp
ON wp.WeekDefinitionId = wd.Id
AND wp.PackageId = c.PackageId
AND wp.IsDeleted = 0
WHERE c.Id IN (SELECT CycleId FROM @Cycles);
SELECT N'Users' AS Entity, COUNT(*) AS Cnt FROM CMS.Users WHERE Id = @UserId
UNION ALL SELECT N'OtpTokens', COUNT(*) FROM CMS.OtpTokens WHERE Mobile IN (@UserMobile, @MobileNorm, @Mobile)
UNION ALL SELECT N'UserInvitations', COUNT(*) FROM CMS.UserInvitations WHERE InviterUserId = @UserId OR InviteeUserId = @UserId
UNION ALL SELECT N'ClubMemberships', COUNT(*) FROM @ClubMemberships
UNION ALL SELECT N'ClubMembershipCycles', COUNT(*) FROM @Cycles
UNION ALL SELECT N'UserPackagePurchases', COUNT(*) FROM CMS.UserPackagePurchases WHERE UserId = @UserId
UNION ALL SELECT N'PaymentTransactions', COUNT(*) FROM CMS.PaymentTransactions WHERE UserId = @UserId OR Mobile = @UserMobile
UNION ALL SELECT N'UserWallets', COUNT(*) FROM @Wallets
UNION ALL SELECT N'UserWalletHistories', COUNT(*) FROM CMS.UserWalletHistories WHERE WalletId IN (SELECT WalletId FROM @Wallets)
UNION ALL SELECT N'Transactions(collected)', COUNT(*) FROM @Tx
UNION ALL SELECT N'NetworkChildren', @ChildCount
UNION ALL SELECT N'UserOrders', COUNT(*) FROM @Orders
UNION ALL SELECT N'DiscountOrders', COUNT(*) FROM @DiscountOrders
UNION ALL SELECT N'UserCommissionPayouts', COUNT(*) FROM @Payouts
UNION ALL SELECT N'PoolReversals(planned)', COUNT(*) FROM @PoolReversals WHERE Fee > 0;
SELECT N'--- Pool reverse plan ---' AS Info;
SELECT * FROM @PoolReversals;
IF @ReversePool = 1 AND @AbortIfPoolCalculated = 1
AND EXISTS (SELECT 1 FROM @PoolReversals WHERE IsCalculated = 1 AND Fee > 0)
BEGIN
RAISERROR(N'One or more target weekly pools are already calculated. Aborting (@AbortIfPoolCalculated=1).', 16, 1);
RETURN;
END;
IF @DryRun = 1
BEGIN
PRINT N'DryRun=1 — no changes committed.';
RETURN;
END;
BEGIN TRANSACTION;
BEGIN TRY
/* ---- Reverse weekly pool charges FIRST (needs cycle + package data) ---- */
IF @ReversePool = 1
BEGIN
UPDATE wp
SET wp.TotalPoolAmount = CASE
WHEN wp.TotalPoolAmount >= r.Fee THEN wp.TotalPoolAmount - r.Fee
ELSE 0
END,
wp.LastModified = SYSUTCDATETIME()
FROM CMS.WeeklyCommissionPools wp
INNER JOIN (
SELECT PoolId, SUM(Fee) AS Fee
FROM @PoolReversals
WHERE PoolId IS NOT NULL AND Fee > 0
GROUP BY PoolId
) r ON r.PoolId = wp.Id;
PRINT N'Pool reverse applied for ' + CAST(@@ROWCOUNT AS NVARCHAR(20)) + N' pool row(s).';
END;
/* ---- Club ---- */
DELETE h FROM CMS.ClubMembershipCycleHistories h
WHERE h.UserId = @UserId OR h.ClubMembershipCycleId IN (SELECT CycleId FROM @Cycles);
DELETE FROM CMS.ClubMembershipCycles
WHERE Id IN (SELECT CycleId FROM @Cycles) OR UserId = @UserId;
DELETE FROM CMS.ClubMembershipHistories WHERE UserId = @UserId
OR ClubMembershipId IN (SELECT ClubMembershipId FROM @ClubMemberships);
DELETE FROM CMS.UserClubFeatures WHERE UserId = @UserId
OR ClubMembershipId IN (SELECT ClubMembershipId FROM @ClubMemberships);
DELETE FROM CMS.ClubMemberships WHERE UserId = @UserId;
/* ---- Commission payouts of THIS user only ---- */
DELETE h FROM CMS.CommissionPayoutHistories h
WHERE h.UserId = @UserId OR h.UserCommissionPayoutId IN (SELECT PayoutId FROM @Payouts);
DELETE FROM CMS.UserCommissionPayouts WHERE UserId = @UserId;
/* ---- Network ---- */
DELETE FROM CMS.NetworkWeeklyBalances WHERE UserId = @UserId;
DELETE FROM CMS.NetworkMembershipHistories
WHERE UserId = @UserId OR OldParentId = @UserId OR NewParentId = @UserId;
IF @ChildCount > 0 AND @ReparentChildren = 1
UPDATE CMS.Users SET NetworkParentId = NULL, LastModified = SYSUTCDATETIME()
WHERE NetworkParentId = @UserId;
/* ---- Invitations ---- */
DELETE FROM CMS.UserInvitations
WHERE InviterUserId = @UserId OR InviteeUserId = @UserId;
/* ---- Package / payment ---- */
DELETE FROM CMS.UserPackagePurchases WHERE UserId = @UserId;
DELETE FROM CMS.PaymentTransactions
WHERE UserId = @UserId OR Mobile = @UserMobile OR Mobile = @MobileNorm;
/* ---- Manual / Daya ---- */
DELETE FROM CMS.ManualPayments WHERE UserId = @UserId;
DELETE FROM CMS.DayaLoanContracts WHERE UserId = @UserId;
/* ---- Discount shop ---- */
DELETE d FROM CMS.DiscountOrderDetails d
WHERE d.DiscountOrderId IN (SELECT DiscountOrderId FROM @DiscountOrders);
DELETE FROM CMS.DiscountOrders WHERE UserId = @UserId;
DELETE FROM CMS.DiscountShoppingCarts WHERE UserId = @UserId;
/* ---- Stock ---- */
DELETE FROM CMS.StockMovements
WHERE PerformedByUserId = @UserId
OR OrderId IN (SELECT OrderId FROM @Orders)
OR DiscountOrderId IN (SELECT DiscountOrderId FROM @DiscountOrders);
/* ---- Orders ---- */
DELETE FROM CMS.FactorDetails WHERE OrderId IN (SELECT OrderId FROM @Orders);
DELETE FROM CMS.OrderVATs WHERE OrderId IN (SELECT OrderId FROM @Orders);
DELETE FROM CMS.UserOrders WHERE UserId = @UserId;
/* ---- Wallet ---- */
DELETE FROM CMS.UserWalletHistories WHERE WalletId IN (SELECT WalletId FROM @Wallets);
DELETE FROM CMS.UserWallets WHERE UserId = @UserId;
/* ---- Cart / address / roles / contracts ---- */
DELETE FROM CMS.UserCarts WHERE UserId = @UserId;
DELETE FROM CMS.UserAddresses WHERE UserId = @UserId;
DELETE FROM CMS.UserRoles WHERE UserId = @UserId;
DELETE FROM CMS.UserContracts WHERE UserId = @UserId;
/* ---- Content authored by test user ---- */
UPDATE CMS.BlogPosts SET IsDeleted = 1, LastModified = SYSUTCDATETIME()
WHERE AuthorUserId = @UserId AND IsDeleted = 0;
UPDATE CMS.PublicMessages SET IsDeleted = 1, LastModified = SYSUTCDATETIME()
WHERE CreatedByUserId = @UserId AND IsDeleted = 0;
/* ---- Transactions ---- */
DELETE t
FROM CMS.Transactions t
WHERE t.Id IN (SELECT TransactionId FROM @Tx)
AND NOT EXISTS (SELECT 1 FROM CMS.UserPackagePurchases p WHERE p.TransactionId = t.Id)
AND NOT EXISTS (SELECT 1 FROM CMS.PaymentTransactions p WHERE p.TransactionId = t.Id)
AND NOT EXISTS (SELECT 1 FROM CMS.UserOrders o WHERE o.TransactionId = t.Id)
AND NOT EXISTS (SELECT 1 FROM CMS.ManualPayments m WHERE m.TransactionId = t.Id)
AND NOT EXISTS (SELECT 1 FROM CMS.DayaLoanContracts d WHERE d.TransactionId = t.Id)
AND NOT EXISTS (SELECT 1 FROM CMS.DiscountOrders d WHERE d.TransactionId = t.Id);
/* ---- OTP ---- */
DELETE FROM CMS.OtpTokens
WHERE Mobile IN (@UserMobile, @MobileNorm, ISNULL(@Mobile, N''));
/* ---- User ---- */
IF @HardDeleteUser = 1
DELETE FROM CMS.Users WHERE Id = @UserId;
ELSE
UPDATE CMS.Users
SET IsDeleted = 1, LastModified = SYSUTCDATETIME()
WHERE Id = @UserId;
COMMIT TRANSACTION;
PRINT N'Cleanup completed successfully.';
SELECT N'DONE' AS Status, @UserId AS UserId, @UserMobile AS Mobile,
@HardDeleteUser AS HardDeleted, @ReversePool AS PoolReversed;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
DECLARE @Err NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR(N'Cleanup failed: %s', 16, 1, @Err);
END CATCH
END;
GO
@@ -43,4 +43,5 @@ public class GetAllPaymentTransactionByFilterResponseModel
public long? TransactionId { get; set; } public long? TransactionId { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public DateTimeOffset Created { get; set; } public DateTimeOffset Created { get; set; }
public string? UserFullName { get; set; }
} }
@@ -19,12 +19,11 @@ public class GetAllPaymentTransactionByFilterQueryHandler
.ApplyPaymentTransactionFilter(request.Filter) .ApplyPaymentTransactionFilter(request.Filter)
.ApplyOrder(sortBy: request.SortBy ?? "-Created"); .ApplyOrder(sortBy: request.SortBy ?? "-Created");
return new GetAllPaymentTransactionByFilterResponseDto var models = await (
{ from x in query.PaginatedListAsync(paginationState: request.PaginationState)
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken), join u in _context.Users.AsNoTracking() on x.UserId equals u.Id into ug
Models = await query from u in ug.DefaultIfEmpty()
.PaginatedListAsync(paginationState: request.PaginationState) select new GetAllPaymentTransactionByFilterResponseModel
.Select(x => new GetAllPaymentTransactionByFilterResponseModel
{ {
Id = x.Id, Id = x.Id,
UserId = x.UserId, UserId = x.UserId,
@@ -40,9 +39,23 @@ public class GetAllPaymentTransactionByFilterQueryHandler
OrderId = x.OrderId, OrderId = x.OrderId,
TransactionId = x.TransactionId, TransactionId = x.TransactionId,
Description = x.Description, Description = x.Description,
Created = x.Created Created = x.Created,
UserFullName = u != null
? ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim()
: null
}) })
.ToListAsync(cancellationToken) .ToListAsync(cancellationToken);
foreach (var model in models)
{
if (string.IsNullOrWhiteSpace(model.UserFullName))
model.UserFullName = "—";
}
return new GetAllPaymentTransactionByFilterResponseDto
{
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
Models = models
}; };
} }
} }
@@ -1,3 +1,5 @@
using CMSMicroservice.Application.Common;
namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter; namespace CMSMicroservice.Application.TransactionsCQ.Queries.GetAllTransactionsByFilter;
public class GetAllTransactionsByFilterQueryHandler : IRequestHandler<GetAllTransactionsByFilterQuery, GetAllTransactionsByFilterResponseDto> public class GetAllTransactionsByFilterQueryHandler : IRequestHandler<GetAllTransactionsByFilterQuery, GetAllTransactionsByFilterResponseDto>
@@ -16,10 +18,7 @@ public class GetAllTransactionsByFilterQueryHandler : IRequestHandler<GetAllTran
.ApplyTransactionFilter(request.Filter) .ApplyTransactionFilter(request.Filter)
.ApplyOrder(sortBy: request.SortBy ?? "-Created"); .ApplyOrder(sortBy: request.SortBy ?? "-Created");
return new GetAllTransactionsByFilterResponseDto var models = await query
{
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
Models = await query
.PaginatedListAsync(paginationState: request.PaginationState) .PaginatedListAsync(paginationState: request.PaginationState)
.Select(x => new GetAllTransactionsByFilterResponseModel .Select(x => new GetAllTransactionsByFilterResponseModel
{ {
@@ -32,7 +31,93 @@ public class GetAllTransactionsByFilterQueryHandler : IRequestHandler<GetAllTran
Type = x.Type, Type = x.Type,
Created = x.Created Created = x.Created
}) })
.ToListAsync(cancellationToken) .ToListAsync(cancellationToken);
await PopulateUserInfoAsync(models, cancellationToken);
return new GetAllTransactionsByFilterResponseDto
{
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
Models = models
}; };
} }
private async Task PopulateUserInfoAsync(
List<GetAllTransactionsByFilterResponseModel> models,
CancellationToken cancellationToken)
{
if (models.Count == 0)
return;
var txIds = models.Select(m => m.Id).ToList();
var userByTx = new Dictionary<long, long>();
void Merge(IEnumerable<(long TxId, long UserId)> rows)
{
foreach (var (txId, userId) in rows)
{
if (!userByTx.ContainsKey(txId))
userByTx[txId] = userId;
}
}
// Priority: PaymentTransactions → UserPackagePurchases → UserOrders → ManualPayments
var fromPt = await _context.PaymentTransactions.AsNoTracking()
.Where(x => x.TransactionId != null && txIds.Contains(x.TransactionId.Value) && x.UserId != null)
.Select(x => new { TxId = x.TransactionId!.Value, UserId = x.UserId!.Value })
.ToListAsync(cancellationToken);
Merge(fromPt.Select(x => (x.TxId, x.UserId)));
var fromUpp = await _context.UserPackagePurchases.AsNoTracking()
.Where(x => x.TransactionId != null && txIds.Contains(x.TransactionId.Value))
.Select(x => new { TxId = x.TransactionId!.Value, UserId = x.UserId })
.ToListAsync(cancellationToken);
Merge(fromUpp.Select(x => (x.TxId, x.UserId)));
var fromOrders = await _context.UserOrders.AsNoTracking()
.Where(x => x.TransactionId != null && txIds.Contains(x.TransactionId.Value))
.Select(x => new { TxId = x.TransactionId!.Value, UserId = x.UserId })
.ToListAsync(cancellationToken);
Merge(fromOrders.Select(x => (x.TxId, x.UserId)));
var fromManual = await _context.ManualPayments.AsNoTracking()
.Where(x => x.TransactionId != null && txIds.Contains(x.TransactionId.Value))
.Select(x => new { TxId = x.TransactionId!.Value, UserId = x.UserId })
.ToListAsync(cancellationToken);
Merge(fromManual.Select(x => (x.TxId, x.UserId)));
if (userByTx.Count == 0)
{
foreach (var model in models)
model.UserFullName = "—";
return;
}
var userIds = userByTx.Values.Distinct().ToList();
var users = await _context.Users.AsNoTracking()
.Where(u => userIds.Contains(u.Id))
.Select(u => new { u.Id, u.FirstName, u.LastName, u.Mobile })
.ToDictionaryAsync(u => u.Id, cancellationToken);
foreach (var model in models)
{
if (!userByTx.TryGetValue(model.Id, out var userId))
{
model.UserFullName = "—";
continue;
}
model.UserId = userId;
if (users.TryGetValue(userId, out var user))
{
model.Mobile = user.Mobile;
var name = UserDisplayName.From(user.FirstName, user.LastName, user.Mobile, user.Id);
model.UserFullName = string.IsNullOrWhiteSpace(name) ? "—" : name;
}
else
{
model.UserFullName = "—";
}
}
}
} }
@@ -25,4 +25,7 @@ public class GetAllTransactionsByFilterResponseDto
// //
public TransactionType Type { get; set; } public TransactionType Type { get; set; }
public DateTime Created { get; set; } public DateTime Created { get; set; }
public long? UserId { get; set; }
public string? UserFullName { get; set; }
public string? Mobile { get; set; }
} }
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace CMSMicroservice.Infrastructure.Persistence.Migrations namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{ {
[DbContext(typeof(ApplicationDbContext))] [DbContext(typeof(ApplicationDbContext))]
[Migration("20260222160755_u21")] [Migration("20260222155925_u21")]
partial class u21 partial class u21
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -0,0 +1,136 @@
-- =============================================
-- Stored Procedure: GetNetworkTree
-- Description: دریافت درخت شبکه باینری با CTE recursive
-- Author: FourSat Team
-- Updated: 2026-04-30 — اضافه کردن IsNewActivation، PackageName، PackageId
-- منطق هفته‌بندی از ActivatedAt به PackagePurchasedAt تغییر کرد
-- =============================================
CREATE OR ALTER PROCEDURE [CMS].[GetNetworkTree]
@RootUserId BIGINT,
@MaxDepth INT = 100,
@IsClubActive BIT = NULL,
@ActivationWeekDefinitionId BIGINT = NULL
AS
BEGIN
SET NOCOUNT ON;
-- تاریخ شروع و پایان هفته فیلتر
DECLARE @WeekStartDate DATETIME = NULL;
DECLARE @WeekEndDate DATETIME = NULL;
IF @ActivationWeekDefinitionId IS NOT NULL
BEGIN
SELECT @WeekStartDate = StartDate, @WeekEndDate = EndDate
FROM [CMS].[WeekDefinitions]
WHERE Id = @ActivationWeekDefinitionId;
END
-- CTE برای پیمایش درخت باینری به صورت recursive
;WITH NetworkTreeCTE AS (
-- Base case: ریشه درخت
SELECT
u.Id AS UserId,
u.Mobile,
u.FirstName,
u.LastName,
u.ReferralCode,
u.LegPosition,
u.NetworkParentId AS ParentId,
0 AS NetworkLevel,
u.Created AS UserCreated
FROM [CMS].[Users] u
WHERE u.Id = @RootUserId
AND u.IsDeleted = 0
UNION ALL
-- Recursive case: فرزندان
SELECT
u.Id AS UserId,
u.Mobile,
u.FirstName,
u.LastName,
u.ReferralCode,
u.LegPosition,
u.NetworkParentId AS ParentId,
parent.NetworkLevel + 1 AS NetworkLevel,
u.Created AS UserCreated
FROM [CMS].[Users] u
INNER JOIN NetworkTreeCTE parent ON u.NetworkParentId = parent.UserId
WHERE u.IsDeleted = 0
AND parent.NetworkLevel < @MaxDepth
)
SELECT
t.UserId,
t.Mobile,
t.FirstName,
t.LastName,
t.ReferralCode,
t.LegPosition,
t.ParentId,
t.NetworkLevel,
cm.ActivatedAt AS ClubActivatedAt,
ISNULL(cm.IsActive, 0) AS IsClubActive,
-- هفته فعال‌سازی بر اساس Cycle جاری (PackagePurchasedAt)
wd.Id AS ActivationWeekDefinitionId,
wd.DisplayName AS ActivationWeekDisplayName,
-- آیا کاربر در هفته هدف Cycle داشته؟
CASE WHEN cc_target.UserId IS NOT NULL THEN 1 ELSE 0 END AS IsActivatedInTargetWeek,
-- نوع فعال‌سازی: 1=جدید، 0=تمدید/خرید مجدد، NULL=بدون Cycle
CASE
WHEN cc_eff.CycleNumber IS NULL THEN NULL
WHEN cc_eff.CycleNumber = 1 THEN 1
ELSE 0
END AS IsNewActivation,
-- نام پکیج موثر (هفته هدف اگر فیلتر باشد، وگرنه پکیج فعلی)
ISNULL(p_eff.Title, '') AS PackageName,
cc_eff.PackageId AS PackageId,
t.UserCreated
FROM NetworkTreeCTE t
LEFT JOIN [CMS].[ClubMemberships] cm ON cm.UserId = t.UserId AND cm.IsDeleted = 0
-- Cycle جاری — برای نمایش هفته و پکیج وقتی فیلتر هفته نیست
OUTER APPLY (
SELECT TOP 1 CycleNumber, PackageId, PackagePurchasedAt
FROM [CMS].[ClubMembershipCycles]
WHERE UserId = t.UserId AND IsCurrentCycle = 1
) cc_current
-- Cycle هفته هدف — برای IsActivatedInTargetWeek و نوع فعال‌سازی
OUTER APPLY (
SELECT TOP 1 UserId, CycleNumber, PackageId
FROM [CMS].[ClubMembershipCycles]
WHERE UserId = t.UserId
AND @WeekStartDate IS NOT NULL
AND PackagePurchasedAt >= @WeekStartDate
AND PackagePurchasedAt < @WeekEndDate
ORDER BY CycleNumber ASC
) cc_target
-- Cycle موثر: اگر هفته فیلتر داریم → cc_target، وگرنه → cc_current
CROSS APPLY (
SELECT
CASE WHEN @WeekStartDate IS NOT NULL AND cc_target.UserId IS NOT NULL
THEN cc_target.CycleNumber ELSE cc_current.CycleNumber END AS CycleNumber,
CASE WHEN @WeekStartDate IS NOT NULL AND cc_target.UserId IS NOT NULL
THEN cc_target.PackageId ELSE cc_current.PackageId END AS PackageId
) cc_eff
-- پکیج موثر
LEFT JOIN [CMS].[Packages] p_eff ON p_eff.Id = cc_eff.PackageId AND p_eff.IsDeleted = 0
-- هفته بر اساس PackagePurchasedAt سایکل جاری
LEFT JOIN [CMS].[WeekDefinitions] wd
ON cc_current.PackagePurchasedAt >= wd.StartDate
AND cc_current.PackagePurchasedAt < wd.EndDate
WHERE (@IsClubActive IS NULL OR ISNULL(cm.IsActive, 0) = @IsClubActive OR t.NetworkLevel = 0)
ORDER BY t.NetworkLevel, t.ParentId, t.LegPosition
OPTION (MAXRECURSION 0);
END
@@ -23,7 +23,7 @@ public class TwentyCardPaymentService : IPaymentGatewayService
private static readonly JsonSerializerOptions JsonOptions = new() private static readonly JsonSerializerOptions JsonOptions = new()
{ {
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}; };
@@ -58,7 +58,9 @@ public class TwentyCardPaymentService : IPaymentGatewayService
{ {
try try
{ {
var amount = ((long)request.Amount).ToString(); // ۲۰کارت amount را به ریال می‌گیرد (تومان × ۱۰)
var amountRial = ((long)request.Amount) * 10L;
var amount = amountRial.ToString();
var payerName = string.IsNullOrWhiteSpace(request.Name) var payerName = string.IsNullOrWhiteSpace(request.Name)
? (string.IsNullOrWhiteSpace(request.Mobile) ? "کاربر" : request.Mobile) ? (string.IsNullOrWhiteSpace(request.Mobile) ? "کاربر" : request.Mobile)
: request.Name.Trim(); : request.Name.Trim();
@@ -85,8 +87,8 @@ public class TwentyCardPaymentService : IPaymentGatewayService
AddFormField(content, "national_id", request.NationalId); AddFormField(content, "national_id", request.NationalId);
_logger.LogInformation( _logger.LogInformation(
"TwentyCard payment request: Amount={Amount} Toman, User={UserId}, Invoice={Invoice}", "TwentyCard payment request: Amount={AmountToman} Toman ({AmountRial} Rial), User={UserId}, Invoice={Invoice}",
amount, request.UserId, invoice); (long)request.Amount, amountRial, request.UserId, invoice);
var response = await _httpClient.PostAsync("api/pgw/merchant/request", content, cancellationToken); var response = await _httpClient.PostAsync("api/pgw/merchant/request", content, cancellationToken);
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
@@ -169,7 +171,7 @@ public class TwentyCardPaymentService : IPaymentGatewayService
["terminal_username"] = _terminalUsername, ["terminal_username"] = _terminalUsername,
["terminal_password"] = _terminalPassword, ["terminal_password"] = _terminalPassword,
["authority"] = refId, ["authority"] = refId,
["amount"] = ((long)amountInToman).ToString() ["amount"] = (((long)amountInToman) * 10L).ToString()
}); });
var response = await _httpClient.PostAsync("api/pgw/merchant/verify", content, cancellationToken); var response = await _httpClient.PostAsync("api/pgw/merchant/verify", content, cancellationToken);
@@ -281,7 +283,10 @@ public class TwentyCardPaymentService : IPaymentGatewayService
private sealed class TwentyCardRequestData private sealed class TwentyCardRequestData
{ {
[JsonPropertyName("authority")]
public string? Authority { get; set; } public string? Authority { get; set; }
[JsonPropertyName("redirectUrl")]
public string? RedirectUrl { get; set; } public string? RedirectUrl { get; set; }
} }
@@ -3,7 +3,7 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>0.0.213</Version> <Version>0.0.214</Version>
<DebugType>None</DebugType> <DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols> <DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -66,6 +66,7 @@ message PaymentTransactionModel
google.protobuf.Int64Value transaction_id = 13; google.protobuf.Int64Value transaction_id = 13;
string description = 14; string description = 14;
google.protobuf.Timestamp created_at = 15; google.protobuf.Timestamp created_at = 15;
string user_full_name = 16;
} }
message GetPaymentTransactionSummaryRequest message GetPaymentTransactionSummaryRequest
@@ -192,6 +192,9 @@ message GetAllTransactionsByFilterResponseModel
messages.TransactionType type = 7; messages.TransactionType type = 7;
} }
google.protobuf.Timestamp created = 8; google.protobuf.Timestamp created = 8;
google.protobuf.Int64Value user_id = 9;
string user_full_name = 10;
google.protobuf.StringValue mobile = 11;
} }
message GetTransactionSummaryRequest message GetTransactionSummaryRequest
@@ -46,7 +46,8 @@ public class PaymentTransactionProfile : IRegister
.Map(dest => dest.OrderId, src => src.OrderId ?? string.Empty) .Map(dest => dest.OrderId, src => src.OrderId ?? string.Empty)
.Map(dest => dest.TransactionId, src => src.TransactionId) .Map(dest => dest.TransactionId, src => src.TransactionId)
.Map(dest => dest.Description, src => src.Description ?? string.Empty) .Map(dest => dest.Description, src => src.Description ?? string.Empty)
.Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created)); .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTimeOffset(src.Created))
.Map(dest => dest.UserFullName, src => src.UserFullName ?? string.Empty);
config.NewConfig<ProtoPt.GetPaymentTransactionSummaryRequest, AppPtSummary.GetPaymentTransactionSummaryQuery>() config.NewConfig<ProtoPt.GetPaymentTransactionSummaryRequest, AppPtSummary.GetPaymentTransactionSummaryQuery>()
.Map(dest => dest.Filter, src => src.Filter); .Map(dest => dest.Filter, src => src.Filter);
@@ -40,7 +40,10 @@ public class TransactionsProfile : IRegister
.Map(dest => dest.PaymentDate, src => src.PaymentDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.PaymentDate.Value, DateTimeKind.Utc)) : null) .Map(dest => dest.PaymentDate, src => src.PaymentDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.PaymentDate.Value, DateTimeKind.Utc)) : null)
.Map(dest => dest.RefId, src => src.RefId) .Map(dest => dest.RefId, src => src.RefId)
.Map(dest => dest.Type, src => src.Type) .Map(dest => dest.Type, src => src.Type)
.Map(dest => dest.Created, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc))); .Map(dest => dest.Created, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)))
.Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.UserFullName, src => src.UserFullName ?? string.Empty)
.Map(dest => dest.Mobile, src => src.Mobile);
config.NewConfig<ProtoTx.GetTransactionSummaryRequest, AppTxSummary.GetTransactionSummaryQuery>() config.NewConfig<ProtoTx.GetTransactionSummaryRequest, AppTxSummary.GetTransactionSummaryQuery>()
.Map(dest => dest.Filter, src => src.Filter); .Map(dest => dest.Filter, src => src.Filter);
+2 -2
View File
@@ -1,12 +1,12 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM 194.5.195.53:32082/dotnet/aspnet:9.0 AS base FROM 185.252.31.44:32082/dotnet/aspnet:9.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
EXPOSE 443 EXPOSE 443
FROM 194.5.195.53:32082/dotnet/sdk:9.0 AS build FROM 185.252.31.44:32082/dotnet/sdk:9.0 AS build
WORKDIR /src WORKDIR /src
COPY ["CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj", "CMSMicroservice.WebApi/"] COPY ["CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj", "CMSMicroservice.WebApi/"]
COPY ["CMSMicroservice.Application/CMSMicroservice.Application.csproj", "CMSMicroservice.Application/"] COPY ["CMSMicroservice.Application/CMSMicroservice.Application.csproj", "CMSMicroservice.Application/"]
@@ -0,0 +1,114 @@
{
"PaymentProvider": "zarinpal",
"ZarinPal": {
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
"UseSandbox": false
},
"TwentyCard": {
"BaseUrl": "https://gateway.20card.ir/ipg",
"TerminalId": "165674",
"TerminalUsername": "ea5y1biehi",
"TerminalPassword": "SaLamAT@1404!",
"CreditPortalUrl": "https://userpanel.20card.ir/",
"DefaultEmail": "info@kbs2.ir",
"Packages": {
"4": {
"GatewayAmountToman": 9700000,
"WalletCreditToman": 13000000
},
"1": {
"GatewayAmountToman": 97000000,
"WalletCreditToman": 130000000
}
}
},
"CmsBaseUrl": "https://cms.kbs2.ir",
"FrontOfficeBaseUrl": "https://kbs2.ir",
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
"JwtIssuer": "https://localhost",
"JwtAudience": "https://localhost",
"JwtExpiryInDays": 5,
"ConnectionStrings": {
"DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;",
"providerName": "System.Data.SqlClient"
},
"Otp": {
"Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M=",
"MasterCode": "1122334455"
},
"Monitoring": {
"SentryEnabled": false,
"SentryDsn": "",
"SlackEnabled": false,
"SlackWebhookUrl": "",
"EmailAlertsEnabled": false,
"AdminEmails": [
"admin@example.com"
],
"SmsNotificationsEnabled": false,
"SmsApiKey": "",
"SmsGatewayUrl": ""
},
"Email": {
"Enabled": true,
"SmtpHost": "smtp.gmail.com",
"SmtpPort": 587,
"SmtpUsername": "your-email@gmail.com",
"SmtpPassword": "your-app-password",
"FromEmail": "noreply@foursat.com",
"FromName": "FourSat CMS",
"EnableSsl": true
},
"Sms": {
"Enabled": true,
"Provider": "Kavenegar",
"KavenegarApiKey": "43676A4E786A6C50452F2F6252507939346A5145764B33566B456454374E657A614468706658656D6534593D",
"Sender": "1000001110100"
},
"DayaPayment": {
"BaseUrl": "https://api.daya.ir",
"ApiKey": "YOUR_DAYA_API_KEY"
},
"DayaApi": {
"UseMock": false,
"BaseAddress": "https://Dayadiamond.ir",
"MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq",
"CacheDurationMinutes": 20
},
"Chatika": {
"Enabled": true,
"BaseUrl": "https://api.chatika.ir",
"ApiKey": "tIukvL8dnV4cB3yVWcCD9Xyfbj8rBxm5wPt2mLyJCgTsBBoMTWjt6mFEqQwpw-er"
},
"BackgroundJobs": {
"WeeklyCommissionCalculation": {
"Enabled": false,
"CronExpression": "5 0 * * 0"
}
},
"SeedWorkers": {
"MagicWalletCycleSeed": {
"Enabled": true
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
},
"Authentication": {
"Authority": "https://ids.domain.com/",
"Audience": "domain_api"
},
"Seq": {
"ServerUrl": "http://seq-svc:5341",
"ApiKey": "oxpvpUzU1pZxMS4s3Fqq"
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -14,12 +14,12 @@
"DefaultEmail": "info@kbs2.ir", "DefaultEmail": "info@kbs2.ir",
"Packages": { "Packages": {
"4": { "4": {
"GatewayAmountToman": 1000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 13000000 "WalletCreditToman": 12656000
}, },
"1": { "1": {
"GatewayAmountToman": 1000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 130000000 "WalletCreditToman": 12656000
} }
} }
}, },
@@ -33,7 +33,7 @@
"JwtAudience": "https://localhost", "JwtAudience": "https://localhost",
"JwtExpiryInDays": 5, "JwtExpiryInDays": 5,
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Data Source=194.5.195.53,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False", "DefaultConnection": "Data Source=185.252.31.44,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False",
"providerName": "System.Data.SqlClient" "providerName": "System.Data.SqlClient"
}, },
"Otp": { "Otp": {
+22 -19
View File
@@ -2,7 +2,7 @@
"PaymentProvider": "zarinpal", "PaymentProvider": "zarinpal",
"ZarinPal": { "ZarinPal": {
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8", "MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
"UseSandbox": true "UseSandbox": false
}, },
"TwentyCard": { "TwentyCard": {
"BaseUrl": "https://gateway.20card.ir/ipg", "BaseUrl": "https://gateway.20card.ir/ipg",
@@ -13,31 +13,23 @@
"DefaultEmail": "info@kbs2.ir", "DefaultEmail": "info@kbs2.ir",
"Packages": { "Packages": {
"4": { "4": {
"GatewayAmountToman": 9700000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 13000000 "WalletCreditToman": 12656000
}, },
"1": { "1": {
"GatewayAmountToman": 97000000, "GatewayAmountToman": 11200000,
"WalletCreditToman": 130000000 "WalletCreditToman": 12656000
} }
} }
}, },
"CmsBaseUrl": "https://localhost:32846", "CmsBaseUrl": "https://cms.kbs2.ir",
"FrontOfficeBaseUrl": "http://localhost:5268", "FrontOfficeBaseUrl": "https://kbs2.ir",
"FMS": {
"Address": "https://dl.afrino.co"
},
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
"JwtIssuer": "https://localhost", "JwtIssuer": "https://localhost",
"JwtAudience": "https://localhost", "JwtAudience": "https://localhost",
"JwtExpiryInDays": 5, "JwtExpiryInDays": 5,
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1AndHttp2"
}
},
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Data Source=194.5.195.53,31433; Initial Catalog=Foursat;User ID=sa;Password=87zH26nbqT;Connection Timeout=300000;MultipleActiveResultSets=True;Encrypt=False", "DefaultConnection": "Server=45.149.79.127,31433;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;",
"providerName": "System.Data.SqlClient" "providerName": "System.Data.SqlClient"
}, },
"Otp": { "Otp": {
@@ -69,7 +61,7 @@
"Sms": { "Sms": {
"Enabled": true, "Enabled": true,
"Provider": "Kavenegar", "Provider": "Kavenegar",
"KavenegarApiKey": "43676A4E786A6C50452F2F6252507939346A5145764B33566B456454374E657A614468706658656D6534593D", "KavenegarApiKey": "497263626F32626A48685A6137524C4F78575A766E4C74694A556B79317648424964655030682B554545413D",
"Sender": "1000001110100" "Sender": "1000001110100"
}, },
"DayaPayment": { "DayaPayment": {
@@ -89,7 +81,7 @@
}, },
"BackgroundJobs": { "BackgroundJobs": {
"WeeklyCommissionCalculation": { "WeeklyCommissionCalculation": {
"Enabled": true, "Enabled": false,
"CronExpression": "5 0 * * 0" "CronExpression": "5 0 * * 0"
} }
}, },
@@ -102,12 +94,23 @@
} }
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
},
"Authentication": { "Authentication": {
"Authority": "https://ids.domain.com/", "Authority": "https://ids.domain.com/",
"Audience": "domain_api" "Audience": "domain_api"
}, },
"Seq": { "Seq": {
"ServerUrl": "https://seq.afrino.co", "ServerUrl": "http://seq-svc:5341",
"ApiKey": "oxpvpUzU1pZxMS4s3Fqq" "ApiKey": "oxpvpUzU1pZxMS4s3Fqq"
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
} }
} }
+2 -2
View File
@@ -3,8 +3,8 @@
<packageSources> <packageSources>
<clear /> <clear />
<!-- Nexus (hosts FourSat packages + proxies nuget.org) --> <!-- Nexus (hosts FourSat packages + proxies nuget.org) -->
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json" allowInsecureConnections="true" /> <add key="Nexus" value="http://185.252.31.44:32081/repository/nuget-all/index.json" allowInsecureConnections="true" />
<add key="foursat-hosted" value="http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json" allowInsecureConnections="true" /> <add key="foursat-hosted" value="http://185.252.31.44:32081/repository/foursat-nuget-hosted/index.json" allowInsecureConnections="true" />
</packageSources> </packageSources>
<packageSourceCredentials> <packageSourceCredentials>
<Nexus> <Nexus>