Author SHA1 Message Date
Hamza-Ayed 58310330b0 feat: add automated SSH expect script and update docker-compose configuration 2026-09-26 15:15:42 +03:00
Hamza-Ayed 9aa45ad605 feat: add sovereign map server migration runbook, seeding scripts, and tactical maneuver compare view 2026-09-26 13:24:51 +03:00
Hamza-Ayed 5028d20881 feat: add border corridor analysis, gap-filler detection, PTZ camera geolocation, and 3D terrain/satellite visualization controls 2026-09-26 00:06:52 +03:00
Hamza-Ayed cb91e5f0de fix: resolve MapLibre worker rendering regression by configuring custom worker and updating geometry type filters 2026-09-24 18:49:40 +03:00
Hamza-Ayed 043cd052b6 fix(tactical): resolve MapLibre line-dasharray crash and add self-healing layers for viewshed, LOS, and artillery 2026-09-24 13:23:38 +03:00
Hamza-Ayed 8dbb336e0a feat: add Jordan places migration scripts and enhance map controller with style-load safety and camera controls 2026-09-23 20:56:34 +03:00
Hamza-Ayed dc9a736e26 MENA region data enrichment scripts` -> Wait, let's keep it concise and clean.
*   *Option 3:* `feat: add tourist landmarks support with UI sheets, data models
2026-09-21 05:21:44 +03:00
Hamza-Ayed cff5a8e1ce chore: remove obsolete python patch scripts and update API key fallback handling in frontend views 2026-09-20 00:59:59 +03:00
Hamza-Ayed e8f5de585d fix: remove landing page nav tab and prevent crowdsource contribution modal from auto-opening on tourism map 2026-09-20 00:46:06 +03:00
Hamza-Ayed ce31d79ba6 feat: add place gate population scripts, map style assets, automated cron sync tasks, and expand dashboard and landing applications 2026-09-19 21:22:05 +03:00
Hamza-Ayed 82b1045f9c feat: bind Landing Page explicitly to #landing route and dedicated Landing Page nav button 2026-09-19 12:56:50 +03:00
Hamza-Ayed e819186201 feat: make Mapbox-grade Landing Page the default home portal with standalone landing.html page 2026-09-19 12:55:15 +03:00
Hamza-Ayed 126c94df63 feat: add Mapbox-grade sovereign portal landing page with interactive showcase, unit economics ROI simulator, and developer playground 2026-09-19 12:36:10 +03:00
Hamza-Ayed 335aa299da chore: update .gitignore to exclude sqlite routing packages 2026-09-19 12:34:35 +03:00
Hamza-Ayed 0bf13824c9 feat: add Jordan 3D topographic platform, Siro Map cinematic orbit studio, sun-shadow simulation, and sovereign intelligence security gating 2026-09-19 12:34:26 +03:00
Hamza-Ayed 91e9e71ce4 feat: introduce routing status tracking for approved roads and telemetry batch queue limits 2026-09-19 12:34:26 +03:00
Hamza-Ayed a8470eb76c feat: add device authentication, place gates support, and PiP navigation features 2026-09-19 12:34:26 +03:00
Hamza-Ayed 279c9fa510 feat: add Google Play store assets and documentation alongside UI copy updates 2026-09-19 12:34:26 +03:00
Hamza-Ayed ce896df30a feat: add device authentication, place gates support, and PiP navigation features 2026-09-19 12:34:26 +03:00
Hamza-Ayed 55830beee8 feat: add vehicle customization, Arabic search normalization, and smooth movement interpolation 2026-09-19 12:34:25 +03:00
Hamza-Ayed 43ad2e0ad4 chore: add build artifacts, map assets, and routing data 2026-09-19 12:34:24 +03:00
Hamza-Ayed aa2b9f131f fix(tactical): implement dynamic MGRS grid conversion, triangle of error resection accuracy, clean surveyed IDW elevation, and robust offline sync validation 2026-09-14 14:21:30 +03:00
Hamza-Ayed 17e8f8eeac refactor(tactical-app): modernize state management with GetX controllers, bindings, and clean MVP architecture 2026-08-22 15:27:53 +03:00
Hamza-Ayed 5e3be672ee feat(tactical-app): full sovereign military suite in Flutter with first-run onboarding, artillery ballistics, HLZ assessment, minefield breach, isochrone, symbols, and overlays 2026-08-22 15:24:09 +03:00
Hamza-Ayed c10fd2396c feat(tactical): sovereign military suite with 100% real satellite DEM intervisibility, viewshed 360, offline engines, and entitlements 2026-08-22 15:11:16 +03:00
Hamza-Ayed 4a7859f4c2 feat: integrate CocoaPods for macos tactical app and update environment configurations 2026-08-21 04:12:13 +03:00
Hamza-Ayed 7216d86740 feat(dem): Universal analytical Jordan DEM surface model for all routes across the Kingdom 2026-08-21 02:55:15 +03:00
Hamza-Ayed 96b22d1fc0 fix(elevation): Restore smooth non-zero continuous terrain differentials 2026-08-21 02:49:41 +03:00
Hamza-Ayed 8a0cf35116 fix(slope): Remove artificial high-frequency noise and calibrate gentle urban gradients 2026-08-21 02:46:02 +03:00
Hamza-Ayed ad4b5d991c feat(topography): Multi-scale micro-terrain and polyline sub-segment elevation tracker 2026-08-21 02:39:58 +03:00
Hamza-Ayed ff97821fd0 fix(elevation): Continuous smooth IDW DEM model for realistic gradients without jumps 2026-08-21 02:34:39 +03:00
Hamza-Ayed 398daceffb fix(routing): Friendly Arabic diagnostic messages for out-of-boundary / off-grid points 2026-08-21 02:26:38 +03:00
Hamza-Ayed 0849b7b53c feat(pricing): Automated Monthly Jordan Fuel Pricing Service via Gemini AI and Cron Schedule 2026-08-21 02:19:08 +03:00
Hamza-Ayed f14d40dac7 feat(routing): Dynamic traffic delay & descent-aware mechanical braking advice 2026-08-21 02:15:56 +03:00
Hamza-Ayed 7572d6b4c5 feat(api & web): Implement real-time Eco Routing, Fuel & Energy Consumption and Cost Engine 2026-08-21 01:58:00 +03:00
Hamza-Ayed e1e634fcb3 fix(docker): Inject VITE_API_KEY environment variable into web frontend container 2026-08-21 00:10:28 +03:00
Hamza-Ayed 429496dd3c fix(web): Add AlertTriangle import and remove hardcoded API key fallback 2026-08-21 00:09:16 +03:00
Hamza-Ayed d7119a53eb fix(security & web): Enforce strict ApiKeyGuard and fix missing Gauge import in App.tsx 2026-08-21 00:07:34 +03:00
Hamza-Ayed 50ecf9bfbf fix(api): Allow trusted portal domains to use demo master API key seamlessly 2026-08-21 00:02:54 +03:00
Hamza-Ayed 9e65afae59 fix(web): Add default master API key fallback for web route calculation 2026-08-21 00:01:11 +03:00
Hamza-Ayed 81d88ee770 fix(api): Use estimateElevation in tactical.service.ts 2026-08-20 23:56:41 +03:00
Hamza-Ayed 431d1f95ff feat: Add Intaleq Native Android Kotlin & iOS Swift SDKs, Incline slope routing, Topographic contours and modular tactical app 2026-08-20 23:54:58 +03:00
Hamza-Ayed 4f6595b1eb feat(showcase): add Autonomous Gemini Vision Map Verification section to executive showcase future roadmap 2026-08-19 17:00:54 +03:00
Hamza-Ayed 52a912380f fix(roads): split parameterized queries into individual statements to satisfy node-postgres prepared statement rule 2026-08-19 16:46:59 +03:00
Hamza-Ayed 06895d6720 fix(roads): replace ON CONFLICT with delete and insert for bulletproof candidate approval 2026-08-19 16:44:59 +03:00
Hamza-Ayed 313c2f641f fix(roads): explicit cast geometry::geometry on insert to approved_roads 2026-08-19 16:38:00 +03:00
Hamza-Ayed 956dae8e75 fix(roads): add ALTER TABLE migration for candidate_id column in approved_roads table 2026-08-19 16:34:42 +03:00
Hamza-Ayed b98ce56622 fix(roads): fix road approval & deletion persistence, add robust endpoints, delete candidate from DB upon reject/delete, and add clear all pending button 2026-08-19 16:29:24 +03:00
Hamza-Ayed 3d13d31101 feat(weather): add real-time animated cloud and precipitation radar layer, integrate early warning badges, refine showcase text, and remove floating CTA button 2026-08-19 16:14:33 +03:00
Hamza-Ayed 470d6d959d fix(showcase): resolve scrolling lock, inject Apple Arabic typography globally, add sticky quick-jump floating subnav, and format continuous executive presentation 2026-08-18 20:24:04 +03:00
Hamza-Ayed aa2967964f feat(showcase): upgrade strategic executive presentation with Apple typography, glassmorphic UI, live tactical telemetry simulator, 8 C4ISR modules, and 2026+ future vision roadmap 2026-08-18 20:15:20 +03:00
Hamza-Ayed ad231c6f20 feat(tactical): integrate cross-country terrain slope, basalt rocks, and off-road mobility analysis into Isochrone engine 2026-08-18 15:40:18 +03:00
Hamza-Ayed 9088af0534 feat(tactical): implement real road-network ray-casting routing for Isochrone reachability 2026-08-18 15:31:56 +03:00
Hamza-Ayed fc4be2b8fd fix: resolve isochroneLoading variable reference in TacticalDefenseView 2026-08-18 15:19:15 +03:00
Hamza-Ayed 3109d782d3 feat(tactical): add interactive Isochrone reachability analysis for hospitals and civil defense 2026-08-18 15:17:37 +03:00
Hamza-Ayed ac4a74d0e0 fix(deps): align @nestjs/common and @nestjs/core to 11.2.1 to resolve sse-signal decorator module error 2026-08-18 14:49:09 +03:00
Hamza-Ayed c28d7f236e feat(tactical): add isochrone emergency reachability endpoint and clean build config 2026-08-18 14:16:40 +03:00
Hamza-Ayed 0d21c1bfbf chore: update sync script file paths 2026-08-18 14:02:40 +03:00
Hamza-Ayed bab24892c7 chore(cleanup): archive one-time migrations, secure telemetry endpoint, and purge temporary files 2026-08-18 14:00:20 +03:00
Hamza-Ayed cb384765fb feat(security): implement dynamic tactical license gate, instant key revocation, and clean hardcoded credentials 2026-08-18 13:46:02 +03:00
Hamza-Ayed 8375773898 fix(web): position map source switcher below navbar and fix missing Flame icon import in ExecutiveShowcase 2026-08-18 13:05:36 +03:00
Hamza-Ayed 5d813f3a3f fix(web): use pre-seeded master API key by default so platform opens seamlessly without auth prompts 2026-08-18 13:02:00 +03:00
Hamza-Ayed f1e31cb721 refactor(tactical): remove ridge lines to keep map uncluttered and focused strictly on wadi flows 2026-08-18 12:54:35 +03:00
Hamza-Ayed 13ff137359 fix(tactical): implement scientific D8 hydrological flow routing for natural wadis and isolate from ridges 2026-08-18 12:44:58 +03:00
Hamza-Ayed c9b3249aa5 fix(web): add shared DemSource singleton, add ErrorBoundary, and conditional view rendering to prevent WebGL crash 2026-08-18 12:35:17 +03:00
Hamza-Ayed ba4ae15eab feat(routing): smart street extraction and distinctive naming for main and alternative routes 2026-08-18 12:25:10 +03:00
Hamza-Ayed 0a854c4302 feat(routing): pure Arabic localization for all navigation instructions (translate Continue onto, toward, keep right, etc.) 2026-08-18 12:18:06 +03:00
Hamza-Ayed 57e3da7ea4 feat(routing): enable elevation profile and steep slope/incline warnings in routing API and frontend 2026-08-18 12:10:32 +03:00
Hamza-Ayed 06543ee8e0 feat(executive): redesign executive strategic showcase with Apple Light theme, SF Pro typography, animations, and full C4ISR tactical modules 2026-08-18 11:57:24 +03:00
Hamza-Ayed ba2f211438 fix(tactical): remove label spam on terrain grid, refine realistic cliff gradient >= 28 deg, and soften overlay opacities 2026-08-18 11:43:28 +03:00
Hamza-Ayed c35f6e61f2 feat(tactical): high-resolution 21x21 DEM terrain study, bilinear interpolation, cliff/rock barrier detection, and 4-tier slope classification 2026-08-18 11:37:48 +03:00
Hamza-Ayed b039281972 feat(tactical): real DEM grid slope analysis, dynamic contour lines, and topography-driven wadi/ridge features 2026-08-18 11:15:56 +03:00
Hamza-Ayed b1ee482830 feat(tactical): add map visualization layers for peaks, valleys, slope sectors, wadis, roads, urban, and basalt hazards 2026-08-18 10:56:58 +03:00
Hamza-Ayed 6bd0bc0ce5 feat(tactical): add comprehensive Terrain Study (دراسة الأرض) with natural/manmade obstacles and OAKOC intelligence report 2026-08-18 10:42:04 +03:00
Hamza-Ayed 91c36d99c9 fix(tactical): resolve click stale closures, add direct coordinate inputs, and draggable markers 2026-08-18 10:24:26 +03:00
Hamza-Ayed 23dde3c8e3 feat(tactical): add interactive location picker, minefield quantities analysis, and organizational feasibility study 2026-08-18 10:15:15 +03:00
Hamza-Ayed 97b8b27ddf feat(tactical): enhance symbols palette, instructions, and fix header layout spacing 2026-08-18 09:55:52 +03:00
Hamza-Ayed 2691eb68eb fix(web): fix tactical map style.json and implement keep-alive views for instant tab switching 2026-08-18 09:49:58 +03:00
Hamza-Ayed 9247499f50 fix(tactical): sync complete tactical service with LOS and artillery methods 2026-08-18 09:47:04 +03:00
Hamza-Ayed 941420f5c0 fix(tactical): resolve DTO types and TS1272 build error for isolatedModules 2026-08-18 09:46:04 +03:00
Hamza-Ayed bd287d155b style(showcase): apple SF Pro typography and refined executive palette 2026-08-17 19:33:21 +03:00
Hamza-Ayed a0a5a09135 feat(tactical): add tactical line of sight API, elevation engine, dynamic step resolution, weather module and executive showcase 2026-08-17 19:25:39 +03:00
Hamza-Ayed 03cdc6fe9c feat: implement geometry snapping logic for manual road insertion and approved roads topology 2026-08-16 17:34:06 +03:00
Hamza-Ayed b3ffa7c71d fix: remove undefined getRefs call and clean up unused HERE checks in CompareView 2026-08-16 14:14:11 +03:00
Hamza-Ayed 844774bd1b feat: add Soviet Military Topo and Google Topo, remove HERE API dependency, relocate and restyle floating ViewNav 2026-08-16 14:09:02 +03:00
Hamza-Ayed 126761004c feat: enhance basemaps palette with Google Hybrid and Esri Clarity, remove carto, instant road approve update 2026-08-16 13:53:38 +03:00
Hamza-Ayed 8061f3e7a3 feat: add road refinement controller and HERE maps in CompareView 2026-08-16 13:35:09 +03:00
Hamza-Ayed 3136a2b7aa feat: sync scripts, scraper, and sql tools from server 2026-08-16 13:19:10 +03:00
Hamza-Ayed 32865e78df Backup before syncing server scripts 2026-08-16 13:17:56 +03:00
1027 changed files with 443545 additions and 117978 deletions
-9
View File
@@ -1,9 +0,0 @@
Downloading Syria map data...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
␍ 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0␍100 239 100 239 0 0 829 0 --:--:-- --:--:-- --:--:-- 829
Warning: Failed to open the file
Warning: /home/hamzadoctor/app/infrastructure/osm-data/syria-latest.osm.pbf:
Warning: No such file or directory
␍ 0 73.8M 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (23) Failure writing output to destination, passed 4096 returned 4294967295
+4
View File
@@ -44,3 +44,7 @@ PAYMOB_IFRAME_ID=837992
BINANCE_PAY_API_KEY="المفتاح_الخاص_بك_هنا"
BINANCE_PAY_SECRET_KEY="المفتاح_السري_الخاص_بك_هنا"
# Google Gemini AI API Key (For Tactical AI Strategic Advisor & Fuel Pricing Intelligence)
GEMINI_API_KEY=
+64 -12
View File
@@ -1,28 +1,46 @@
# Database Dumps
# Database Dumps & Archives
*.sql
*.tar
*.tar.gz
*.zip
*.mbtiles
*.db
*.db-*
*.db.gz
# Node modules
node_modules/
.npm/
# Node modules and Web build output
**/node_modules/
**/dist/
**/.npm/
**/.npm-cache/
*.tsbuildinfo
# Environment Variables
# Environment Variables & Secrets
.env
.env.local
.env.*
*.env
*.env.*
**/.env
**/.env.*
*.key
*.pem
# Virtual Environments
# Python Virtual Environments
venv/
venv_overture/
.venv/
.venv*/
env/
__pycache__/
*.pyc
# Map Data
# Map Data & Intermediate Geodata
*.osm.pbf
*.pbf
infrastructure/osm-data/default-gh/
apps/.npm-cache/
*.json.bak
apps/api/src/telemetry/*.json
**/dem_tiles/
# OS generated files
.DS_Store
@@ -32,6 +50,40 @@ apps/api/src/telemetry/*.json
.Trashes
ehthumbs.db
Thumbs.db
.venv-osm/
__pycache__/
infrastructure/osm-data/*.orig
# IDE & Editor files
.idea/
.vscode/
*.iml
*.ipr
*.iws
# Flutter / Dart Mobile SDKs (all packages)
**/.dart_tool/
**/.flutter-plugins
**/.flutter-plugins-dependencies
**/.packages
**/.pub-cache/
**/.pub/
**/build/
**/coverage/
# Android build artifacts
**/.gradle/
**/android/.gradle/
**/android/app/build/
**/android/app/debug/
**/android/app/release/
**/android/app/profile/
*.apk
*.aab
# iOS build artifacts
**/ios/Pods/
**/ios/.symlinks/
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/.last_build_id
**/ios/DerivedData/
*.ipa
+25
View File
@@ -0,0 +1,25 @@
.git/
.github/
.idea/
.vscode/
.DS_Store
node_modules/
build/
.dart_tool/
.gradle/
Pods/
.symlinks/
*.apk
*.aab
*.ipa
dem_tiles/
infrastructure/osm-data/*.osm.pbf
infrastructure/osm-data/dem_tiles/
infrastructure/osm-data/valhalla-work/
venv/
.venv*/
apps/api/dist/
apps/web/dist/
packages/*/dist/
.npm/
.npm-cache/
+13 -1
View File
@@ -45,9 +45,21 @@ A complete self-hosted mapping platform prototype for Jordan, built with OpenStr
- **GraphHopper Latest**
- **MapLibre GL JS**
## 🎖️ البوابات السيادية والعسكرية / Sovereign & Defense Portals
- **بوابة المنظومة العسكرية والتوجيه التكتيكي الميداني (المعتمد للجهات العسكرية)**:
`https://map-saas.intaleqapp.com/sovereign-tactical-landing.html`
- **مركز القيادة التفاعلي (رَصْد)**:
`https://map-saas.intaleqapp.com/sovereign-command.html`
- **المنظومة التكتيكية الميدانية الحية**:
`https://map-saas.intaleqapp.com/#tactical`
- **وثيقة الإيجاز الأمني والمعمارية التقنية (المركز الجغرافي الملكي)**:
`https://map-saas.intaleqapp.com/security_brief_rjgc.html`
## 📝 Arabic Comments / ملاحظات باللغة العربية
تم تصميم هذا النظام ليكون نواة لمنصة خرائط تجارية تدعم تطبيقات التوصيل والنقل الذكي في الأردن، مع التركيز على استيعاب بيانات الموقع اللحظية للسائقين كل 3 ثواني ومعالجتها مكانياً.
تم تصميم هذا النظام ليكون نواة لمنصة خرائط تجارية وسيادية تدعم تطبيقات التوصيل والنقل الذكي والأنظمة التكتيكية في الأردن، مع التركيز على استيعاب بيانات الموقع اللحظية للسائقين كل 3 ثواني ومعالجتها مكانياً واستقلالية العمل 100% بدون إنترنت.
## ⚠️ Limitations / القيود الحالية
- Does not include full Map Matching for traffic light delay logic (Phase 2).
- Requires manual download of MBTiles for high-performance tile serving in production.
+402
View File
@@ -0,0 +1,402 @@
# دليل الانتقال السيادي الشامل وخطة استعادة المنصة والنسخ الاحتياطي
## Sovereign Map Platform: Server Migration, Disaster Recovery & Offline Hosting Runbook
**المهندس المعماري والاستراتيجي للمنظومة:** حمزة عايد (Hamza Ayed)
*Founding Tech Architect & Sovereign Mobility Strategist*
**المنصة:** سيرو للخرائط السيادية والذكاء المكاني والتكتيكي (Siro Sovereign Map Engine / Map-SaaS)
**تاريخ التوثيق:** أيلول (سبتمبر) 2026
**الإصدار:** v2.4 Enterprise Sovereign Edition
---
## 1. الفلسفة السيادية وهيكلية النظام (Architecture Topology)
صُممت المنظومة لتكون **مستقلة سيادياً 100% (Air-Gapped Ready)**، بحيث لا تعتمد على أي خادم سحابي أجنبي أو مفاتيح اشتراك شهرية من (Google Maps, Mapbox, AWS, Esri) لتشغيل خرائط وتضاريس الأردن ثلاثية الأبعاد والتوجيه الميداني.
```
[ Internet / VPN / Tactical Mesh ]
│
[ CloudPanel / Nginx ]
(SSL: map-saas.intaleqapp.com, tiles, api)
│
┌──────────────┬──────────────┬────────┴──────┬──────────────┬──────────────┐
│ :3201 │ :3204 │ :3200 │ :3202 │ :8989 │
┌────┴─────┐ ┌──────┴──────┐ ┌─────┴──────┐ ┌──────┴──────┐ ┌─────┴──────┐
│ map-web │ │map-dashboard│ │ map-api │ │ map-martin │ │map-routing │
│ (React + │ │(Admin/Usage │ │ (NestJS + │ │(Rust Vector │ │(GraphHopper│
│ MapLibre)│ │ Developer) │ │ Spatial) │ │ + MBTiles) │ │ Engine) │
└────┬─────┘ └─────────────┘ └─────┬──────┘ └──────┬──────┘ └─────┬──────┘
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┬──────────────┐ │
│ │ map-db │ map-redis │ │
│ │(PostGIS 15 + │ (Cache & Bus │ │
│ │ Topology) │ Port 6381) │ │
│ └──────────────┴──────────────┘ │
│ │
└──────────────────────────[ Volumes ]───────────────────────┘
• postgres_data (جداول وخطوط الكنتور والطرق)
• data/mbtiles (تضاريس 3D DEM + أقمار صناعية)
• infrastructure/osm-data (شبكة الطرق الخام)
```
---
## 2. جدول أحجام البيانات وسيادتها (Data Inventory & Sizing)
نطاق التغطية الجغرافي للمملكة الأردنية الهاشمية (Jordan Bounding Box):
- **خط طول (Longitude):** من `34.8° E` إلى `39.3° E`
- **دائرة عرض (Latitude):** من `29.1° N` إلى `33.4° N`
| نوع البيانات (Data Asset) | المصدر والتقنية | مستويات التكبير (Zoom) | الحجم الفعلي التقريبي | مكان التخزين بالسيرفر |
| :--- | :--- | :--- | :--- | :--- |
| **قاعدة بيانات PostGIS المكانية** | PostgreSQL 15 + PostGIS 3.3 | N/A (Geometries) | ~1.5 - 2.8 GB (مضغوط) | حاوية `map-db` (Volume: `postgres_data`) |
| **خطوط الكنتور الطبوغرافية** | 10m/20m Index Contours | PostGIS Layer | ضمن قاعدة البيانات | جدول `jordan_contours` / Martin |
| **تضاريس الارتفاع 3D DEM** | Terrarium PNG (Sovereign MBTiles) | Zoom 0 إلى 14 (63.6k بلاطة) | ~930 MB | `data/mbtiles/jordan-dem.mbtiles` |
| **صور الأقمار الصناعية (Satellite)** | High-Res Orthophoto MBTiles | Zoom 0 إلى 14 (63.6k بلاطة) | ~1.5 GB (~6 GB لـ z15) | `data/mbtiles/jordan-satellite.mbtiles` |
| **خريطة الأردن المفتوحة الخام** | OpenStreetMap (Geofabrik) | PBF Binary Format | ~30 MB | `infrastructure/osm-data/jordan-latest.osm.pbf` |
| **حزم التوجيه الطرقي والتكتيكي** | GraphHopper & Valhalla Graphs | Graph Storage | ~150 - 300 MB | `infrastructure/osm-data/routing-packages/` |
| **الحجم الإجمالي المطلوب لنقل المملكة بالكامل** | **حزمة متكاملة سيادياً** | **شامل التضاريس والفضائي** | **~4.5 إلى 9 GB فقط!** | يمكن نقله في دقائق عبر SSH |
---
## 3. المرحلة الأولى: أخذ النسخة الاحتياطية من السيرفر الحالي (Source Server Backup)
### الخيار (أ): التشغيل التلقائي عبر السكربت الذكي (Automated Script)
قمنا ببرمجة سكربت أوتوماتيكي يقوم بتفريغ الداتابيز، وفحص الـ Redis، ونسخ الإعدادات والخرائط وتوليد بصمات `SHA256`:
```bash
# الانتقال لمسار المشروع
cd /home/hamzadoctor/app
# تشغيل سكربت النسخ الاحتياطي الشامل
./infrastructure/scripts/backup_sovereign_map.sh
```
ينتج السكربت ملفاً مضغوطاً بصيغة:
`/home/hamzadoctor/backups/sovereign_map_full_backup_YYYYMMDD_HHMMSS.tar.gz`
---
### الخيار (ب): الإجراء اليدوي خطوة بخطوة (Manual Fallback)
إذا أردت تنفيذ النسخ يدوياً بأوامر مباشرة:
#### 1. تفريغ قاعدة بيانات PostGIS بصيغة Custom Dump السريعة:
```bash
# إنشاء مجلد النسخ
mkdir -p /home/hamzadoctor/backups/manual_backup
# تصدير الداتابيز بصيغة -Fc (تدعم الاستعادة المتوازية واستعادة الجداول المحددة)
docker exec map-db pg_dump -U mapuser -d mapdb -Fc > /home/hamzadoctor/backups/manual_backup/mapdb_custom.dump
# تصدير مخطط الـ Schema بصيغة SQL للقراءة والتدقيق:
docker exec map-db pg_dump -U mapuser -d mapdb --schema-only > /home/hamzadoctor/backups/manual_backup/mapdb_schema.sql
```
#### 2. حفظ كاش وبيانات Redis:
```bash
docker exec map-redis redis-cli bgsave
docker cp map-redis:/data/dump.rdb /home/hamzadoctor/backups/manual_backup/redis_dump.rdb
```
#### 3. جمع ملفات التكوين والبيئة والأنماط:
```bash
mkdir -p /home/hamzadoctor/backups/manual_backup/configs
cp /home/hamzadoctor/app/.env /home/hamzadoctor/backups/manual_backup/configs/.env.backup
cp /home/hamzadoctor/app/docker-compose*.yml /home/hamzadoctor/backups/manual_backup/configs/
cp -r /home/hamzadoctor/app/style*.json /home/hamzadoctor/backups/manual_backup/configs/
```
#### 4. ضغط الحزمة بالكامل:
```bash
cd /home/hamzadoctor/backups
tar -czvf sovereign_map_manual_backup.tar.gz manual_backup/
```
---
## 4. المرحلة الثانية: تجهيز السيرفر الجديد ومواصفاته (Target Server Setup)
### المواصفات العتادية الموصى بها (Hardware Specs):
- **المعالج:** 4 vCPU (أو 8 vCPU لأداء فائق أثناء معالجة غراف الطرق).
- **الذاكرة العشوائية (RAM):** 8 GB كحد أدنى (يُفضل 16 GB لضمان استقرار GraphHopper Heap دون OOM).
- **التخزين:** 80 GB NVMe SSD (لقاعدة البيانات والتضاريس وسجلات النظام).
- **نظام التشغيل:** Ubuntu 22.04 LTS أو Ubuntu 24.04 LTS x86_64.
### إعداد السيرفر الجديد برمجياً:
#### 1. تحديث النظام وتثبيت حزم الأدوات الأساسية:
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git rsync htop ufw unzip jq sqlite3
```
#### 2. ضبط ملف المبادلة (Swap File - حاسم جداً لمنع OOM):
بسبب استهلاك محرك GraphHopper للذاكرة أثناء بناء شبكة التوجيه، يجب إنشاء 4GB Swap:
```bash
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
```
#### 3. تثبيت Docker Engine و Docker Compose V2 الرسمي:
```bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
```
#### 4. ضبط الجدار الناري (UFW Firewall):
```bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (Reverse Proxy)
sudo ufw allow 443/tcp # HTTPS (SSL)
sudo ufw enable
```
*(ملاحظة أمنية: لا تفتح المنافذ الداخلية 5432، 6381، 3200، 3201، 3202 للعامة، بل اتركها خلف الـ Nginx Reverse Proxy الداخلي).*
---
## 5. المرحلة الثالثة: نقل البيانات بين السيرفرين (Direct Server-to-Server Sync)
### الطريقة الموصى بها: النقل المباشر فائق السرعة عبر `rsync`:
نفّذ الأمر التالي من السيرفر المصدر (أو من جهاز التحكم الخاص بك):
```bash
# نقل ملف النسخة الاحتياطية الشاملة إلى السيرفر الجديد
rsync -avzP -e "ssh -i ~/.ssh/your-key" \
/home/hamzadoctor/backups/sovereign_map_full_backup_*.tar.gz \
root@<NEW_SERVER_IP>:/home/hamzadoctor/backups/
```
### استنساخ مستودع الكود إلى السيرفر الجديد:
على السيرفر الجديد:
```bash
mkdir -p /home/hamzadoctor/app
cd /home/hamzadoctor/app
# سحب الكود من Git أو نقله عبر rsync
git clone <YOUR_GIT_REPO_URL> .
# أو نسخ ملفات المشروع عبر rsync من السيرفر القديم:
# rsync -avzP --exclude 'node_modules' --exclude '.git' root@<OLD_SERVER_IP>:/home/hamzadoctor/app/ /home/hamzadoctor/app/
```
---
## 6. المرحلة الرابعة: استعادة النظام وتشغيله على السيرفر الجديد (Restoration & Bootstrap)
### الخيار (أ): الاستعادة التلقائية الذكية (One-Command Restore)
على السيرفر الجديد داخل مجلد المشروع:
```bash
cd /home/hamzadoctor/app
# تشغيل سكربت الاستعادة وتزويده بمسار ملف النسخة الاحتياطية
./infrastructure/scripts/restore_sovereign_map.sh /home/hamzadoctor/backups/sovereign_map_full_backup_YYYYMMDD_HHMMSS.tar.gz
```
يقوم هذا السكربت بـ:
1. استخراج الإعدادات والتأكد من ملف `.env`.
2. تشغيل حاوية `map-db` والانتظار حتى تصبح جاهزة للحقن.
3. تفعيل إضافات PostGIS واستعادة كافة الجداول والفهارس المكانية بدقة.
4. تنفيذ `VACUUM ANALYZE` لضمان أقصى سرعة لاستعلامات الخرائط.
5. بناء وتشغيل كافة الخدمات التابعة (`map-api`, `map-martin`, `map-web`, `map-dashboard`, `map-routing`).
6. فحص صحة المنافذ والتأكد من استجابتها.
---
### الخيار (ب): الاستعادة اليدوية خطوة بخطوة
```bash
cd /home/hamzadoctor/app
# 1. تجهيز ملف البيئة
cp .env.example .env
# قم بمراجعة وتعديل كلمات المرور في .env إذا أردت
# 2. تشغيل قاعدة البيانات والكاش
docker compose up -d db redis
# 3. التأكد من جهوزية قاعدة البيانات
docker exec map-db pg_isready -U mapuser -d mapdb
# 4. تفعيل PostGIS واستعادة الداتابيز
docker exec -i map-db psql -U mapuser -d mapdb -c "CREATE EXTENSION IF NOT EXISTS postgis;"
docker exec -i map-db psql -U mapuser -d mapdb -c "CREATE EXTENSION IF NOT EXISTS postgis_topology;"
docker exec -i map-db pg_restore -U mapuser -d mapdb --clean --if-exists --no-owner --no-acl < /path/to/mapdb_custom.dump
docker exec -i map-db psql -U mapuser -d mapdb -c "VACUUM ANALYZE;"
# 5. تشغيل وبناء بقية الخدمات
docker compose up -d --build api martin web dashboard routing
```
---
## 7. المرحلة الخامسة: التوليد والتشغيل الأوفلاين للتضاريس والأقمار الصناعية (Offline MBTiles)
لضمان عمل المنظومة التكتيكية **في حال انقطاع الإنترنت بالكامل أو في شبكات العمليات المغلقة**:
### 1. تشغيل مولدات التخزين السيادية (Pre-seeding Jordan Tiles):
قمنا بتجهيز سكربتات بايثون متعددة المسارات (Multi-threaded) لتحميل وتخزين مربعات الأردن كاملة في ملفات SQLite MBTiles قياسية:
```bash
cd /home/hamzadoctor/app
# إنشاء مجلد ملفات MBTiles
mkdir -p data/mbtiles
# تحميل وتخزين تضاريس الأردن ثلاثية الأبعاد (DEM z0 إلى z14 ~ 930MB)
python3 infrastructure/scripts/seed_jordan_dem.py --format mbtiles --threads 16 --output data/mbtiles/jordan-dem.mbtiles
# تحميل وتخزين صور الأقمار الصناعية للأردن (Satellite z0 إلى z14 ~ 1.5GB)
python3 infrastructure/scripts/seed_jordan_satellite.py --format mbtiles --threads 16 --max-zoom 14 --output data/mbtiles/jordan-satellite.mbtiles
```
### 2. بث الـ MBTiles محلياً عبر خادم Martin:
خادم Martin يدعم بث ملفات MBTiles فورياً دون الحاجة لأي خادم إضافي!
في ملف `docker-compose.yml`، نقوم بربط مجلد `data/mbtiles`:
```yaml
martin:
image: maplibre/martin:latest
container_name: map-martin
ports:
- "3202:3000"
environment:
- WATCH_DB=true
volumes:
- ./data/mbtiles:/mbtiles:ro
command: ["/mbtiles", "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"]
```
بذلك يوفر خادم Martin الروابط المحلية التالية بسرعة استجابة فائقة (Sub-millisecond):
- **تضاريس DEM 3D:** `https://tiles.intaleqapp.com/jordan-dem/{z}/{x}/{y}`
- **صور الأقمار:** `https://tiles.intaleqapp.com/jordan-satellite/{z}/{x}/{y}`
- **الطبقات المتجهة والكنتور:** `https://tiles.intaleqapp.com/jordan_contours/{z}/{x}/{y}`
---
## 8. المرحلة السادسة: إعداد الـ Reverse Proxy وشهادات SSL (CloudPanel / Nginx)
قم بتوجيه النطاقات (DNS A-Records) إلى IP السيرفر الجديد، ثم أضف الإعدادات التالية في Nginx أو عبر واجهة CloudPanel:
### 1. بوابة الويب والمناورة التكتيكية (`map-saas.intaleqapp.com`):
```nginx
server {
listen 80;
server_name map-saas.intaleqapp.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name map-saas.intaleqapp.com;
# شهادات Let's Encrypt
ssl_certificate /etc/letsencrypt/live/map-saas.intaleqapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/map-saas.intaleqapp.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3201;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### 2. خادم مربعات الخرائط والتضاريس السيادي (`tiles.intaleqapp.com`):
```nginx
server {
listen 443 ssl http2;
server_name tiles.intaleqapp.com;
ssl_certificate /etc/letsencrypt/live/tiles.intaleqapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tiles.intaleqapp.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3202;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# تفعيل كاش المتصفح للبلاطات
expires 7d;
add_header Cache-Control "public, no-transform";
add_header Access-Control-Allow-Origin *;
}
}
```
### 3. الواجهة الخلفية والذكاء المكاني (`api.map.intaleqapp.com`):
```nginx
location / {
proxy_pass http://127.0.0.1:3200;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
```
---
## 9. الفحوصات والتدقيق التشغيلي بعد الانتقال (Verification Checklist)
نفّذ هذه الأوامر على السيرفر الجديد للتأكد من سلامة كافة الأجزاء بنسبة 100%:
```bash
# 1. فحص حاويات Docker
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 2. فحص استجابة الـ API
curl -I http://localhost:3200/api/health
# 3. فحص خادم البلاطات Martin
curl -s http://localhost:3202/catalog | jq .
# 4. فحص اتصال قاعدة البيانات PostGIS والتأكد من جداول خطوط الكنتور
docker exec -it map-db psql -U mapuser -d mapdb -c "
SELECT table_name, (xpath('/row/cnt/text()', xml_count))[1]::text::int as row_count
FROM (
SELECT table_name, query_to_xml(format('select count(*) as cnt from %I', table_name), false, true, '') as xml_count
FROM information_schema.tables
WHERE table_schema = 'public' AND table_name IN ('jordan_contours', 'planet_osm_roads', 'planet_osm_line')
) t;
"
# 5. فحص محرك التوجيه GraphHopper
curl -I http://localhost:8989/health
```
---
## 10. جدول الصيانة والنسخ الاحتياطي التلقائي اليومي (Daily Automated Cron)
لضمان عدم فقدان أي بيانات مستقبلية، قم بإضافة مهمة مجدولة (Cron Job) لتوليد نسخة احتياطية مشفرة كل ليلة في الساعة 3:00 فجراً والاحتفاظ بآخر 7 أيام تلقائياً:
```bash
# فتح جدول المهام
crontab -e
```
أضف السطر التالي:
```cron
0 3 * * * /home/hamzadoctor/app/infrastructure/scripts/backup_sovereign_map.sh /home/hamzadoctor/backups >> /home/hamzadoctor/backups/backup.log 2>&1
# حذف النسخ التي يتجاوز عمرها 7 أيام تلقائياً لتوفير المساحة:
30 3 * * * find /home/hamzadoctor/backups -name "sovereign_map_full_backup_*.tar.gz" -mtime +7 -delete
```
---
## 11. خلاصة المهندس المعماري (Architect Summary)
> **"السيادة الرقمية في نظم المعلومات الجغرافية والتكتيكية ليست رفاهية؛ إنها الضمان الحقيقي لاستمرار العمليات الحيوية تحت أي ظرف سياسي أو انقطاع للبنية التحتية العالمية. هذه الحزمة تتيح لك نقل منظومة دولة كاملة وتشغيلها على أي سيرفر أو جهاز ميداني في أقل من 30 دقيقة بكفاءة تشغيلية مطلقة وتكلفة بنية تحتية تقترب من الصفر."**
> — *حمزة عايد (Hamza Ayed)*
-6
View File
@@ -1,6 +0,0 @@
import { AppService } from './app.service';
export declare class AppController {
private readonly appService;
constructor(appService: AppService);
getHello(): string;
}
-35
View File
@@ -1,35 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppController = void 0;
const common_1 = require("@nestjs/common");
const app_service_1 = require("./app.service");
let AppController = class AppController {
appService;
constructor(appService) {
this.appService = appService;
}
getHello() {
return this.appService.getHello();
}
};
exports.AppController = AppController;
__decorate([
(0, common_1.Get)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", String)
], AppController.prototype, "getHello", null);
exports.AppController = AppController = __decorate([
(0, common_1.Controller)(),
__metadata("design:paramtypes", [app_service_1.AppService])
], AppController);
//# sourceMappingURL=app.controller.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["../src/app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,+CAA2C;AAGpC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACK;IAA7B,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAGvD,QAAQ;QACN,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACpC,CAAC;CACF,CAAA;AAPY,sCAAa;AAIxB;IADC,IAAA,YAAG,GAAE;;;;6CAGL;wBANU,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAE8B,wBAAU;GADxC,aAAa,CAOzB"}
-2
View File
@@ -1,2 +0,0 @@
export declare class AppModule {
}
-65
View File
@@ -1,65 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppModule = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const config_1 = require("@nestjs/config");
const typeorm_1 = require("@nestjs/typeorm");
const schedule_1 = require("@nestjs/schedule");
const telemetry_module_1 = require("./telemetry/telemetry.module");
const maps_module_1 = require("./maps/maps.module");
const geocoding_module_1 = require("./geocoding/geocoding.module");
const auth_module_1 = require("./auth/auth.module");
const throttler_1 = require("@nestjs/throttler");
const usage_module_1 = require("./usage/usage.module");
const billing_module_1 = require("./billing/billing.module");
const mail_module_1 = require("./common/mail.module");
const usage_interceptor_1 = require("./usage/usage.interceptor");
let AppModule = class AppModule {
};
exports.AppModule = AppModule;
exports.AppModule = AppModule = __decorate([
(0, common_1.Module)({
imports: [
config_1.ConfigModule.forRoot({
isGlobal: true,
}),
schedule_1.ScheduleModule.forRoot(),
typeorm_1.TypeOrmModule.forRootAsync({
imports: [config_1.ConfigModule],
inject: [config_1.ConfigService],
useFactory: (config) => ({
type: 'postgres',
url: config.get('DATABASE_URL'),
autoLoadEntities: true,
synchronize: false,
}),
}),
throttler_1.ThrottlerModule.forRoot([{
ttl: 60000,
limit: 10,
}]),
auth_module_1.AuthModule,
telemetry_module_1.TelemetryModule,
maps_module_1.MapsModule,
geocoding_module_1.GeocodingModule,
usage_module_1.UsageModule,
billing_module_1.BillingModule,
mail_module_1.MailModule,
],
controllers: [],
providers: [
{
provide: core_1.APP_INTERCEPTOR,
useClass: usage_interceptor_1.UsageInterceptor,
},
],
})
], AppModule);
//# sourceMappingURL=app.module.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uCAA0D;AAC1D,2CAA6D;AAC7D,6CAAgD;AAChD,+CAAkD;AAClD,mEAA+D;AAC/D,oDAAgD;AAChD,mEAA+D;AAC/D,oDAAgD;AAChD,iDAAoD;AACpD,uDAAmD;AACnD,6DAAyD;AACzD,sDAAkD;AAClD,iEAA6D;AAsCtD,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IApCrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;aACf,CAAC;YACF,yBAAc,CAAC,OAAO,EAAE;YACxB,uBAAa,CAAC,YAAY,CAAC;gBACzB,OAAO,EAAE,CAAC,qBAAY,CAAC;gBACvB,MAAM,EAAE,CAAC,sBAAa,CAAC;gBACvB,UAAU,EAAE,CAAC,MAAqB,EAAE,EAAE,CAAC,CAAC;oBACtC,IAAI,EAAE,UAAU;oBAChB,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;oBAC/B,gBAAgB,EAAE,IAAI;oBACtB,WAAW,EAAE,KAAK;iBACnB,CAAC;aACH,CAAC;YACF,2BAAe,CAAC,OAAO,CAAC,CAAC;oBACvB,GAAG,EAAE,KAAK;oBACV,KAAK,EAAE,EAAE;iBACV,CAAC,CAAC;YACH,wBAAU;YACV,kCAAe;YACf,wBAAU;YACV,kCAAe;YACf,0BAAW;YACX,8BAAa;YACb,wBAAU;SACX;QACD,WAAW,EAAE,EAAE;QACf,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,sBAAe;gBACxB,QAAQ,EAAE,oCAAgB;aAC3B;SACF;KACF,CAAC;GACW,SAAS,CAAG"}
-3
View File
@@ -1,3 +0,0 @@
export declare class AppService {
getHello(): string;
}
-20
View File
@@ -1,20 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppService = void 0;
const common_1 = require("@nestjs/common");
let AppService = class AppService {
getHello() {
return 'Hello World!';
}
};
exports.AppService = AppService;
exports.AppService = AppService = __decorate([
(0, common_1.Injectable)()
], AppService);
//# sourceMappingURL=app.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"app.service.js","sourceRoot":"","sources":["../src/app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,QAAQ;QACN,OAAO,cAAc,CAAC;IACxB,CAAC;CACF,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"}
-9
View File
@@ -1,9 +0,0 @@
import { OnModuleInit } from '@nestjs/common';
import { AuthService } from './auth.service';
import { ConfigService } from '@nestjs/config';
export declare class AuthModule implements OnModuleInit {
private readonly authService;
private readonly configService;
constructor(authService: AuthService, configService: ConfigService);
onModuleInit(): Promise<void>;
}
-53
View File
@@ -1,53 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthModule = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const auth_service_1 = require("./auth.service");
const tenant_entity_1 = require("./entities/tenant.entity");
const api_key_entity_1 = require("./entities/api-key.entity");
const redis_module_1 = require("../common/redis.module");
const config_1 = require("@nestjs/config");
const tenant_controller_1 = require("./tenant.controller");
const firebase_admin_service_1 = require("./firebase-admin.service");
const firebase_auth_guard_1 = require("./guards/firebase-auth.guard");
let AuthModule = class AuthModule {
authService;
configService;
constructor(authService, configService) {
this.authService = authService;
this.configService = configService;
}
async onModuleInit() {
const defaultKey = this.configService.get('MAP_API_KEY');
if (defaultKey) {
await this.authService.seedDefaultKey('Default System', 'admin@intaleq.xyz', defaultKey);
console.log('✅ Default System API Key seeded successfully');
}
}
};
exports.AuthModule = AuthModule;
exports.AuthModule = AuthModule = __decorate([
(0, common_1.Global)(),
(0, common_1.Module)({
imports: [
typeorm_1.TypeOrmModule.forFeature([tenant_entity_1.Tenant, api_key_entity_1.ApiKey]),
redis_module_1.RedisModule,
],
controllers: [tenant_controller_1.TenantController],
providers: [auth_service_1.AuthService, firebase_admin_service_1.FirebaseAdminService, firebase_auth_guard_1.FirebaseAuthGuard],
exports: [auth_service_1.AuthService, firebase_admin_service_1.FirebaseAdminService, firebase_auth_guard_1.FirebaseAuthGuard],
}),
__metadata("design:paramtypes", [auth_service_1.AuthService, typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], AuthModule);
//# sourceMappingURL=auth.module.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAA8D;AAC9D,6CAAgD;AAChD,iDAA6C;AAC7C,4DAAkD;AAClD,8DAAmD;AACnD,yDAAqD;AACrD,2CAA+C;AAC/C,2DAAuD;AACvD,qEAAgE;AAChE,sEAAiE;AAY1D,IAAM,UAAU,GAAhB,MAAM,UAAU;IAEF;IACA;IAFnB,YACmB,WAAwB,EACxB,aAA4B;QAD5B,gBAAW,GAAX,WAAW,CAAa;QACxB,kBAAa,GAAb,aAAa,CAAe;IAC5C,CAAC;IAMJ,KAAK,CAAC,YAAY;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC;QACjE,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;CACF,CAAA;AAjBY,gCAAU;qBAAV,UAAU;IAVtB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,sBAAM,EAAE,uBAAM,CAAC,CAAC;YAC1C,0BAAW;SACZ;QACD,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,0BAAW,EAAE,6CAAoB,EAAE,uCAAiB,CAAC;QACjE,OAAO,EAAE,CAAC,0BAAW,EAAE,6CAAoB,EAAE,uCAAiB,CAAC;KAChE,CAAC;qCAGgC,0BAAW,sBACT,sBAAa,oBAAb,sBAAa;GAHpC,UAAU,CAiBtB"}
-24
View File
@@ -1,24 +0,0 @@
import { Repository } from 'typeorm';
import { ApiKey } from './entities/api-key.entity';
import { Tenant } from './entities/tenant.entity';
import { RedisService } from '../common/redis.service';
export declare class AuthService {
private readonly apiKeyRepository;
private readonly tenantRepository;
private readonly redisService;
private readonly logger;
constructor(apiKeyRepository: Repository<ApiKey>, tenantRepository: Repository<Tenant>, redisService: RedisService);
validateApiKey(key: string, origin?: string, referer?: string): Promise<{
tenant: Tenant;
apiKey: ApiKey;
rateLimit: number;
}>;
private validateRestrictions;
private isDomainAllowed;
hashSecret(secret: string): string;
seedDefaultKey(name: string, email: string, keyString: string): Promise<void>;
getApiKeys(tenantId: string): Promise<ApiKey[]>;
createApiKey(tenantId: string, name: string, rateLimit?: number, allowedOrigins?: string[]): Promise<ApiKey>;
getDefaultTenant(): Promise<Tenant>;
findOrCreateByFirebaseUid(uid: string, email: string, name: string, photoUrl?: string): Promise<Tenant>;
}
-180
View File
@@ -1,180 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var AuthService_1;
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const crypto_1 = require("crypto");
const api_key_entity_1 = require("./entities/api-key.entity");
const tenant_entity_1 = require("./entities/tenant.entity");
const redis_service_1 = require("../common/redis.service");
let AuthService = AuthService_1 = class AuthService {
apiKeyRepository;
tenantRepository;
redisService;
logger = new common_1.Logger(AuthService_1.name);
constructor(apiKeyRepository, tenantRepository, redisService) {
this.apiKeyRepository = apiKeyRepository;
this.tenantRepository = tenantRepository;
this.redisService = redisService;
}
async validateApiKey(key, origin, referer) {
const cacheKey = `auth:apikey:${key}`;
const cachedData = await this.redisService.get(cacheKey);
if (cachedData) {
this.validateRestrictions(cachedData.apiKey, origin, referer);
return cachedData;
}
const apiKey = await this.apiKeyRepository.findOne({
where: { key, isActive: true },
relations: ['tenant'],
});
if (!apiKey || !apiKey.tenant || !apiKey.tenant.isActive) {
throw new common_1.UnauthorizedException('Invalid or inactive API Key');
}
this.validateRestrictions(apiKey, origin, referer);
const result = {
tenant: apiKey.tenant,
apiKey: apiKey,
rateLimit: apiKey.rateLimit || tenant_entity_1.RATE_LIMITS[apiKey.tenant.plan] || 100,
};
await this.redisService.set(cacheKey, result, 3600);
this.apiKeyRepository.update(apiKey.id, { lastUsedAt: new Date() }).catch(err => this.logger.error(`Failed to update lastUsedAt for API key ${apiKey.id}: ${err.message}`));
return result;
}
validateRestrictions(apiKey, origin, referer) {
if (apiKey.allowedOrigins && apiKey.allowedOrigins.length > 0) {
if (!origin || !this.isDomainAllowed(origin, apiKey.allowedOrigins)) {
this.logger.warn(`Origin mismatch for API key ${apiKey.id}. Received: ${origin}`);
throw new common_1.UnauthorizedException('Request Origin not allowed for this API Key');
}
}
if (apiKey.allowedReferrers && apiKey.allowedReferrers.length > 0) {
if (!referer || !this.isDomainAllowed(referer, apiKey.allowedReferrers)) {
this.logger.warn(`Referer mismatch for API key ${apiKey.id}. Received: ${referer}`);
throw new common_1.UnauthorizedException('Request Referer not allowed for this API Key');
}
}
}
isDomainAllowed(domain, allowedList) {
const cleanDomain = domain.replace(/^https?:\/\//, '').split('/')[0];
return allowedList.some(allowed => {
if (allowed === '*')
return true;
const regex = new RegExp('^' + allowed.replace(/\*/g, '.*') + '$');
return regex.test(cleanDomain);
});
}
hashSecret(secret) {
return (0, crypto_1.createHash)('sha256').update(secret).digest('hex');
}
async seedDefaultKey(name, email, keyString) {
let tenant = await this.tenantRepository.findOne({ where: { email } });
if (!tenant) {
tenant = await this.tenantRepository.save({
name,
email,
isActive: true,
plan: tenant_entity_1.TenantPlan.ENTERPRISE,
});
}
const existingKey = await this.apiKeyRepository.findOne({ where: { key: keyString } });
if (!existingKey) {
await this.apiKeyRepository.save({
key: keyString,
secretHash: this.hashSecret(keyString),
name: 'Default Production Key',
isActive: true,
tenantId: tenant.id,
rateLimit: 1000
});
}
}
async getApiKeys(tenantId) {
return this.apiKeyRepository.find({
where: { tenantId },
order: { createdAt: 'DESC' }
});
}
async createApiKey(tenantId, name, rateLimit, allowedOrigins) {
const existingKeysCount = await this.apiKeyRepository.count({ where: { tenantId } });
if (existingKeysCount >= 1) {
throw new common_1.ConflictException('Limit reached: Only 1 API key allowed per developer currently.');
}
const key = `in_${(0, crypto_1.createHash)('md5').update(Math.random().toString()).digest('hex').substring(0, 24)}`;
const apiKey = this.apiKeyRepository.create({
key,
secretHash: this.hashSecret(key),
name,
tenantId,
rateLimit: rateLimit || 100,
allowedOrigins: allowedOrigins || [],
isActive: true
});
return this.apiKeyRepository.save(apiKey);
}
async getDefaultTenant() {
const tenant = await this.tenantRepository.findOne({ where: {} });
if (!tenant)
throw new common_1.NotFoundException('No tenants found in system');
return tenant;
}
async findOrCreateByFirebaseUid(uid, email, name, photoUrl) {
let tenant = await this.tenantRepository.findOne({ where: { firebaseUid: uid } });
if (!tenant) {
tenant = await this.tenantRepository.findOne({ where: { email } });
if (tenant) {
this.logger.log(`Linking existing tenant ${email} to Firebase UID: ${uid}`);
tenant.firebaseUid = uid;
if (photoUrl)
tenant.photoUrl = photoUrl;
tenant = await this.tenantRepository.save(tenant);
}
else {
this.logger.log(`Creating new tenant for Firebase user: ${email} (${uid})`);
const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com' || email === 'hamzaayedpython@gmail.com';
tenant = await this.tenantRepository.save({
firebaseUid: uid,
email,
name,
photoUrl,
plan: isAdmin ? tenant_entity_1.TenantPlan.ENTERPRISE : tenant_entity_1.TenantPlan.FREE,
isActive: true,
});
}
}
else {
const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com' || email === 'hamzaayedpython@gmail.com';
if (isAdmin && tenant.plan !== tenant_entity_1.TenantPlan.ENTERPRISE) {
tenant.plan = tenant_entity_1.TenantPlan.ENTERPRISE;
await this.tenantRepository.save(tenant);
}
if (tenant.photoUrl !== photoUrl || tenant.name !== name) {
await this.tenantRepository.update(tenant.id, { photoUrl, name });
}
}
return tenant;
}
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = AuthService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(api_key_entity_1.ApiKey)),
__param(1, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, redis_service_1.RedisService])
], AuthService);
//# sourceMappingURL=auth.service.js.map
File diff suppressed because one or more lines are too long
-5
View File
@@ -1,5 +0,0 @@
export declare class CreateKeyDto {
name: string;
rateLimit?: number;
allowedOrigins?: string[];
}
-35
View File
@@ -1,35 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateKeyDto = void 0;
const class_validator_1 = require("class-validator");
class CreateKeyDto {
name;
rateLimit;
allowedOrigins;
}
exports.CreateKeyDto = CreateKeyDto;
__decorate([
(0, class_validator_1.IsString)(),
__metadata("design:type", String)
], CreateKeyDto.prototype, "name", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
__metadata("design:type", Number)
], CreateKeyDto.prototype, "rateLimit", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsArray)(),
(0, class_validator_1.IsString)({ each: true }),
__metadata("design:type", Array)
], CreateKeyDto.prototype, "allowedOrigins", void 0);
//# sourceMappingURL=create-key.dto.js.map
@@ -1 +0,0 @@
{"version":3,"file":"create-key.dto.js","sourceRoot":"","sources":["../../../../src/auth/dto/management/create-key.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA0E;AAE1E,MAAa,YAAY;IAEvB,IAAI,CAAS;IAIb,SAAS,CAAU;IAKnB,cAAc,CAAY;CAC3B;AAZD,oCAYC;AAVC;IADC,IAAA,0BAAQ,GAAE;;0CACE;AAIb;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;+CACQ;AAKnB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,0BAAQ,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;oDACC"}
-16
View File
@@ -1,16 +0,0 @@
import { Tenant } from './tenant.entity';
export declare class ApiKey {
id: string;
key: string;
secretHash: string;
name: string;
isActive: boolean;
rateLimit: number;
allowedOrigins: string[];
allowedReferrers: string[];
tenant: Tenant;
tenantId: string;
lastUsedAt: Date;
createdAt: Date;
updatedAt: Date;
}
-87
View File
@@ -1,87 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiKey = void 0;
const typeorm_1 = require("typeorm");
const tenant_entity_1 = require("./tenant.entity");
let ApiKey = class ApiKey {
id;
key;
secretHash;
name;
isActive;
rateLimit;
allowedOrigins;
allowedReferrers;
tenant;
tenantId;
lastUsedAt;
createdAt;
updatedAt;
};
exports.ApiKey = ApiKey;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
__metadata("design:type", String)
], ApiKey.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)({ unique: true }),
(0, typeorm_1.Index)(),
__metadata("design:type", String)
], ApiKey.prototype, "key", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], ApiKey.prototype, "secretHash", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], ApiKey.prototype, "name", void 0);
__decorate([
(0, typeorm_1.Column)({ default: true }),
__metadata("design:type", Boolean)
], ApiKey.prototype, "isActive", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", Number)
], ApiKey.prototype, "rateLimit", void 0);
__decorate([
(0, typeorm_1.Column)('simple-array', { nullable: true }),
__metadata("design:type", Array)
], ApiKey.prototype, "allowedOrigins", void 0);
__decorate([
(0, typeorm_1.Column)('simple-array', { nullable: true }),
__metadata("design:type", Array)
], ApiKey.prototype, "allowedReferrers", void 0);
__decorate([
(0, typeorm_1.ManyToOne)(() => tenant_entity_1.Tenant, (tenant) => tenant.apiKeys),
__metadata("design:type", tenant_entity_1.Tenant)
], ApiKey.prototype, "tenant", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], ApiKey.prototype, "tenantId", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", Date)
], ApiKey.prototype, "lastUsedAt", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
__metadata("design:type", Date)
], ApiKey.prototype, "createdAt", void 0);
__decorate([
(0, typeorm_1.UpdateDateColumn)(),
__metadata("design:type", Date)
], ApiKey.prototype, "updatedAt", void 0);
exports.ApiKey = ApiKey = __decorate([
(0, typeorm_1.Entity)('api_keys')
], ApiKey);
//# sourceMappingURL=api-key.entity.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"api-key.entity.js","sourceRoot":"","sources":["../../../src/auth/entities/api-key.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAuH;AACvH,mDAAyC;AAGlC,IAAM,MAAM,GAAZ,MAAM,MAAM;IAEjB,EAAE,CAAS;IAIX,GAAG,CAAS;IAGZ,UAAU,CAAS;IAGnB,IAAI,CAAS;IAGb,QAAQ,CAAU;IAGlB,SAAS,CAAS;IAGlB,cAAc,CAAW;IAGzB,gBAAgB,CAAW;IAG3B,MAAM,CAAS;IAGf,QAAQ,CAAS;IAGjB,UAAU,CAAO;IAGjB,SAAS,CAAO;IAGhB,SAAS,CAAO;CACjB,CAAA;AAxCY,wBAAM;AAEjB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;kCACpB;AAIX;IAFC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACxB,IAAA,eAAK,GAAE;;mCACI;AAGZ;IADC,IAAA,gBAAM,GAAE;;0CACU;AAGnB;IADC,IAAA,gBAAM,GAAE;;oCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCACR;AAGlB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACT;AAGlB;IADC,IAAA,gBAAM,EAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CAClB;AAGzB;IADC,IAAA,gBAAM,EAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDAChB;AAG3B;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,sBAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;8BAC5C,sBAAM;sCAAC;AAGf;IADC,IAAA,gBAAM,GAAE;;wCACQ;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BACf,IAAI;0CAAC;AAGjB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;iBAvCL,MAAM;IADlB,IAAA,gBAAM,EAAC,UAAU,CAAC;GACN,MAAM,CAwClB"}
-26
View File
@@ -1,26 +0,0 @@
import { ApiKey } from './api-key.entity';
export declare enum TenantPlan {
FREE = "FREE",
STARTER = "STARTER",
PRO = "PRO",
ENTERPRISE = "ENTERPRISE"
}
export declare enum TenantRole {
USER = "USER",
ADMIN = "ADMIN"
}
export declare const QUOTA_LIMITS: Record<TenantPlan, number>;
export declare const RATE_LIMITS: Record<TenantPlan, number>;
export declare class Tenant {
id: string;
name: string;
email: string;
plan: TenantPlan;
firebaseUid: string;
role: TenantRole;
photoUrl: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
apiKeys: ApiKey[];
}
-108
View File
@@ -1,108 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tenant = exports.RATE_LIMITS = exports.QUOTA_LIMITS = exports.TenantRole = exports.TenantPlan = void 0;
const typeorm_1 = require("typeorm");
const api_key_entity_1 = require("./api-key.entity");
var TenantPlan;
(function (TenantPlan) {
TenantPlan["FREE"] = "FREE";
TenantPlan["STARTER"] = "STARTER";
TenantPlan["PRO"] = "PRO";
TenantPlan["ENTERPRISE"] = "ENTERPRISE";
})(TenantPlan || (exports.TenantPlan = TenantPlan = {}));
var TenantRole;
(function (TenantRole) {
TenantRole["USER"] = "USER";
TenantRole["ADMIN"] = "ADMIN";
})(TenantRole || (exports.TenantRole = TenantRole = {}));
exports.QUOTA_LIMITS = {
[TenantPlan.FREE]: 8000,
[TenantPlan.STARTER]: 25000,
[TenantPlan.PRO]: 100000,
[TenantPlan.ENTERPRISE]: 500000,
};
exports.RATE_LIMITS = {
[TenantPlan.FREE]: 5,
[TenantPlan.STARTER]: 100,
[TenantPlan.PRO]: 500,
[TenantPlan.ENTERPRISE]: 50000,
};
let Tenant = class Tenant {
id;
name;
email;
plan;
firebaseUid;
role;
photoUrl;
isActive;
createdAt;
updatedAt;
apiKeys;
};
exports.Tenant = Tenant;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
__metadata("design:type", String)
], Tenant.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], Tenant.prototype, "name", void 0);
__decorate([
(0, typeorm_1.Column)({ unique: true }),
__metadata("design:type", String)
], Tenant.prototype, "email", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: TenantPlan,
default: TenantPlan.FREE,
}),
__metadata("design:type", String)
], Tenant.prototype, "plan", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true, unique: true }),
__metadata("design:type", String)
], Tenant.prototype, "firebaseUid", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: TenantRole,
default: TenantRole.USER,
}),
__metadata("design:type", String)
], Tenant.prototype, "role", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], Tenant.prototype, "photoUrl", void 0);
__decorate([
(0, typeorm_1.Column)({ default: true }),
__metadata("design:type", Boolean)
], Tenant.prototype, "isActive", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
__metadata("design:type", Date)
], Tenant.prototype, "createdAt", void 0);
__decorate([
(0, typeorm_1.UpdateDateColumn)(),
__metadata("design:type", Date)
], Tenant.prototype, "updatedAt", void 0);
__decorate([
(0, typeorm_1.OneToMany)(() => api_key_entity_1.ApiKey, (apiKey) => apiKey.tenant),
__metadata("design:type", Array)
], Tenant.prototype, "apiKeys", void 0);
exports.Tenant = Tenant = __decorate([
(0, typeorm_1.Entity)('tenants')
], Tenant);
//# sourceMappingURL=tenant.entity.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"tenant.entity.js","sourceRoot":"","sources":["../../../src/auth/entities/tenant.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAgH;AAChH,qDAA0C;AAE1C,IAAY,UAKX;AALD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,iCAAmB,CAAA;IACnB,yBAAW,CAAA;IACX,uCAAyB,CAAA;AAC3B,CAAC,EALW,UAAU,0BAAV,UAAU,QAKrB;AAED,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,6BAAe,CAAA;AACjB,CAAC,EAHW,UAAU,0BAAV,UAAU,QAGrB;AAEY,QAAA,YAAY,GAA+B;IACtD,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI;IACvB,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK;IAC3B,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM;IACxB,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM;CAChC,CAAC;AAEW,QAAA,WAAW,GAA+B;IACrD,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IACpB,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG;IACzB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG;IACrB,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,KAAK;CAC/B,CAAC;AAGK,IAAM,MAAM,GAAZ,MAAM,MAAM;IAEjB,EAAE,CAAS;IAGX,IAAI,CAAS;IAGb,KAAK,CAAS;IAOd,IAAI,CAAa;IAGjB,WAAW,CAAS;IAOpB,IAAI,CAAa;IAGjB,QAAQ,CAAS;IAGjB,QAAQ,CAAU;IAGlB,SAAS,CAAO;IAGhB,SAAS,CAAO;IAGhB,OAAO,CAAW;CACnB,CAAA;AAzCY,wBAAM;AAEjB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;kCACpB;AAGX;IADC,IAAA,gBAAM,GAAE;;oCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;qCACX;AAOd;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,UAAU,CAAC,IAAI;KACzB,CAAC;;oCACe;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;2CACrB;AAOpB;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,UAAU,CAAC,IAAI;KACzB,CAAC;;oCACe;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wCACV;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCACR;AAGlB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,uBAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;;uCACjC;iBAxCP,MAAM;IADlB,IAAA,gBAAM,EAAC,SAAS,CAAC;GACL,MAAM,CAyClB"}
-11
View File
@@ -1,11 +0,0 @@
import { OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as admin from 'firebase-admin';
export declare class FirebaseAdminService implements OnModuleInit {
private configService;
private readonly logger;
private firebaseApp;
constructor(configService: ConfigService);
onModuleInit(): void;
verifyIdToken(token: string): Promise<admin.auth.DecodedIdToken>;
}
-99
View File
@@ -1,99 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var FirebaseAdminService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirebaseAdminService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const admin = __importStar(require("firebase-admin"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
let FirebaseAdminService = FirebaseAdminService_1 = class FirebaseAdminService {
configService;
logger = new common_1.Logger(FirebaseAdminService_1.name);
firebaseApp;
constructor(configService) {
this.configService = configService;
}
onModuleInit() {
const keyPath = this.configService.get('FIREBASE_SERVICE_ACCOUNT_PATH');
if (!keyPath) {
this.logger.error('FIREBASE_SERVICE_ACCOUNT_PATH is not defined in environment variables');
return;
}
try {
const absolutePath = path.isAbsolute(keyPath)
? keyPath
: path.resolve(process.cwd(), keyPath);
if (!fs.existsSync(absolutePath)) {
this.logger.error(`Firebase service account key not found at: ${absolutePath}`);
return;
}
const serviceAccount = JSON.parse(fs.readFileSync(absolutePath, 'utf8'));
this.firebaseApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
this.logger.log('✅ Firebase Admin SDK initialized successfully');
}
catch (error) {
this.logger.error(`Failed to initialize Firebase Admin SDK: ${error.message}`);
}
}
async verifyIdToken(token) {
try {
return await admin.auth().verifyIdToken(token);
}
catch (error) {
this.logger.error(`Token verification failed: ${error.message}`);
throw error;
}
}
};
exports.FirebaseAdminService = FirebaseAdminService;
exports.FirebaseAdminService = FirebaseAdminService = FirebaseAdminService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], FirebaseAdminService);
//# sourceMappingURL=firebase-admin.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"firebase-admin.service.js","sourceRoot":"","sources":["../../src/auth/firebase-admin.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAkE;AAClE,2CAA+C;AAC/C,sDAAwC;AACxC,uCAAyB;AACzB,2CAA6B;AAGtB,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAIX;IAHH,MAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IACxD,WAAW,CAAgB;IAEnC,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAEpD,YAAY;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,+BAA+B,CAAC,CAAC;QAEhF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;YAC3F,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YAEH,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC3C,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;YAEzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,YAAY,EAAE,CAAC,CAAC;gBAChF,OAAO;YACT,CAAC;YAED,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;YAEzE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC;gBACrC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC;aAClD,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAa;QAC/B,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF,CAAA;AA7CY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;yDAKwB,sBAAa,oBAAb,sBAAa;GAJrC,oBAAoB,CA6ChC"}
-10
View File
@@ -1,10 +0,0 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { FirebaseAdminService } from '../firebase-admin.service';
import { AuthService } from '../auth.service';
export declare class FirebaseAuthGuard implements CanActivate {
private firebaseAdminService;
private authService;
private readonly logger;
constructor(firebaseAdminService: FirebaseAdminService, authService: AuthService);
canActivate(context: ExecutionContext): Promise<boolean>;
}
-54
View File
@@ -1,54 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var FirebaseAuthGuard_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirebaseAuthGuard = void 0;
const common_1 = require("@nestjs/common");
const firebase_admin_service_1 = require("../firebase-admin.service");
const auth_service_1 = require("../auth.service");
let FirebaseAuthGuard = FirebaseAuthGuard_1 = class FirebaseAuthGuard {
firebaseAdminService;
authService;
logger = new common_1.Logger(FirebaseAuthGuard_1.name);
constructor(firebaseAdminService, authService) {
this.firebaseAdminService = firebaseAdminService;
this.authService = authService;
}
async canActivate(context) {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new common_1.UnauthorizedException('Missing or invalid Authorization header');
}
const token = authHeader.split('Bearer ')[1];
try {
const decodedToken = await this.firebaseAdminService.verifyIdToken(token);
const tenant = await this.authService.findOrCreateByFirebaseUid(decodedToken.uid, decodedToken.email || '', decodedToken.name || decodedToken.email?.split('@')[0] || 'Unknown User', decodedToken.picture);
if (!tenant.isActive) {
throw new common_1.UnauthorizedException('Tenant account is disabled');
}
request['tenant'] = tenant;
request['user'] = decodedToken;
return true;
}
catch (error) {
this.logger.error(`Firebase Auth failed: ${error.message}`);
throw new common_1.UnauthorizedException(error.message || 'Authentication failed');
}
}
};
exports.FirebaseAuthGuard = FirebaseAuthGuard;
exports.FirebaseAuthGuard = FirebaseAuthGuard = FirebaseAuthGuard_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [firebase_admin_service_1.FirebaseAdminService,
auth_service_1.AuthService])
], FirebaseAuthGuard);
//# sourceMappingURL=firebase-auth.guard.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"firebase-auth.guard.js","sourceRoot":"","sources":["../../../src/auth/guards/firebase-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAMwB;AACxB,sEAAiE;AACjE,kDAA8C;AAGvC,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IAIlB;IACA;IAJO,MAAM,GAAG,IAAI,eAAM,CAAC,mBAAiB,CAAC,IAAI,CAAC,CAAC;IAE7D,YACU,oBAA0C,EAC1C,WAAwB;QADxB,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,gBAAW,GAAX,WAAW,CAAa;IAC/B,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAEjD,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,8BAAqB,CAAC,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7C,IAAI,CAAC;YAEH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAG1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,yBAAyB,CAC7D,YAAY,CAAC,GAAG,EAChB,YAAY,CAAC,KAAK,IAAI,EAAE,EACxB,YAAY,CAAC,IAAI,IAAI,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,cAAc,EACxE,YAAY,CAAC,OAAO,CACrB,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACrB,MAAM,IAAI,8BAAqB,CAAC,4BAA4B,CAAC,CAAC;YAChE,CAAC;YAGD,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;YAE/B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,MAAM,IAAI,8BAAqB,CAAC,KAAK,CAAC,OAAO,IAAI,uBAAuB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;CACF,CAAA;AA5CY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;qCAKqB,6CAAoB;QAC7B,0BAAW;GALvB,iBAAiB,CA4C7B"}
-9
View File
@@ -1,9 +0,0 @@
import { AuthService } from './auth.service';
import { CreateKeyDto } from './dto/management/create-key.dto';
export declare class TenantController {
private readonly authService;
constructor(authService: AuthService);
createKey(req: any, dto: CreateKeyDto): Promise<import("./entities/api-key.entity").ApiKey>;
getKeys(req: any): Promise<import("./entities/api-key.entity").ApiKey[]>;
getMe(req: any): Promise<any>;
}
-74
View File
@@ -1,74 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TenantController = void 0;
const common_1 = require("@nestjs/common");
const auth_service_1 = require("./auth.service");
const create_key_dto_1 = require("./dto/management/create-key.dto");
const swagger_1 = require("@nestjs/swagger");
const firebase_auth_guard_1 = require("./guards/firebase-auth.guard");
let TenantController = class TenantController {
authService;
constructor(authService) {
this.authService = authService;
}
async createKey(req, dto) {
if (req.user && req.user.email_verified === false) {
throw new common_1.ForbiddenException('You must verify your email address before creating an API key.');
}
const tenantId = req.tenant.id;
return this.authService.createApiKey(tenantId, dto.name, dto.rateLimit, dto.allowedOrigins);
}
async getKeys(req) {
const tenantId = req.tenant.id;
return this.authService.getApiKeys(tenantId);
}
async getMe(req) {
return req.tenant;
}
};
exports.TenantController = TenantController;
__decorate([
(0, common_1.Post)('keys'),
(0, swagger_1.ApiOperation)({ summary: 'Create a new API key' }),
__param(0, (0, common_1.Req)()),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, create_key_dto_1.CreateKeyDto]),
__metadata("design:returntype", Promise)
], TenantController.prototype, "createKey", null);
__decorate([
(0, common_1.Get)('keys'),
(0, swagger_1.ApiOperation)({ summary: 'Get all API keys for the authenticated tenant' }),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TenantController.prototype, "getKeys", null);
__decorate([
(0, common_1.Get)('me'),
(0, swagger_1.ApiOperation)({ summary: 'Get current authenticated tenant info' }),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TenantController.prototype, "getMe", null);
exports.TenantController = TenantController = __decorate([
(0, swagger_1.ApiTags)('auth'),
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Controller)('auth/management'),
__metadata("design:paramtypes", [auth_service_1.AuthService])
], TenantController);
//# sourceMappingURL=tenant.controller.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"tenant.controller.js","sourceRoot":"","sources":["../../src/auth/tenant.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAwG;AACxG,iDAA6C;AAC7C,oEAA+D;AAC/D,6CAAuE;AACvE,sEAAiE;AAM1D,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACE;IAA7B,YAA6B,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAInD,AAAN,KAAK,CAAC,SAAS,CACN,GAAQ,EACP,GAAiB;QAEzB,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;YAClD,MAAM,IAAI,2BAAkB,CAAC,gEAAgE,CAAC,CAAC;QACjG,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,CAClC,QAAQ,EACR,GAAG,CAAC,IAAI,EACR,GAAG,CAAC,SAAS,EACb,GAAG,CAAC,cAAc,CACnB,CAAC;IACJ,CAAC;IAIK,AAAN,KAAK,CAAC,OAAO,CAAQ,GAAQ;QAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAIK,AAAN,KAAK,CAAC,KAAK,CAAQ,GAAQ;QACzB,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;CACF,CAAA;AAlCY,4CAAgB;AAKrB;IAFL,IAAA,aAAI,EAAC,MAAM,CAAC;IACZ,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAE/C,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,6BAAY;;iDAa1B;AAIK;IAFL,IAAA,YAAG,EAAC,MAAM,CAAC;IACX,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,+CAA+C,EAAE,CAAC;IAC5D,WAAA,IAAA,YAAG,GAAE,CAAA;;;;+CAGnB;AAIK;IAFL,IAAA,YAAG,EAAC,IAAI,CAAC;IACT,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,uCAAuC,EAAE,CAAC;IACtD,WAAA,IAAA,YAAG,GAAE,CAAA;;;;6CAEjB;2BAjCU,gBAAgB;IAJ5B,IAAA,iBAAO,EAAC,MAAM,CAAC;IACf,IAAA,uBAAa,GAAE;IACf,IAAA,kBAAS,EAAC,uCAAiB,CAAC;IAC5B,IAAA,mBAAU,EAAC,iBAAiB,CAAC;qCAEc,0BAAW;GAD1C,gBAAgB,CAkC5B"}
-36
View File
@@ -1,36 +0,0 @@
import { BillingService } from './billing.service';
import { PayMobProvider } from './providers/paymob.provider';
import { BinanceProvider } from './providers/binance.provider';
import { PaymentProvider } from './entities/transaction.entity';
export declare class BillingController {
private billingService;
private paymobProvider;
private binanceProvider;
private readonly logger;
constructor(billingService: BillingService, paymobProvider: PayMobProvider, binanceProvider: BinanceProvider);
getSubscription(req: any): Promise<import("./entities/subscription.entity").Subscription>;
checkout(req: any, body: {
plan: string;
provider: PaymentProvider;
}): Promise<{
checkoutUrl: any;
orderId: string;
prepayId: any;
} | {
checkoutUrl: string;
orderId: string;
}>;
handlePaymobCallback(query: any, req: any): Promise<string>;
handlePaymobWebhook(body: any, hmac: string): Promise<{
status: string;
message: string;
} | {
status: string;
message?: undefined;
}>;
handleBinanceWebhook(body: any, req: any): Promise<{
returnCode: string;
returnMessage: any;
}>;
getInvoices(req: any): Promise<import("./entities/transaction.entity").Transaction[]>;
}
-259
View File
@@ -1,259 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var BillingController_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BillingController = void 0;
const common_1 = require("@nestjs/common");
const billing_service_1 = require("./billing.service");
const paymob_provider_1 = require("./providers/paymob.provider");
const binance_provider_1 = require("./providers/binance.provider");
const firebase_auth_guard_1 = require("../auth/guards/firebase-auth.guard");
const swagger_1 = require("@nestjs/swagger");
const transaction_entity_1 = require("./entities/transaction.entity");
let BillingController = BillingController_1 = class BillingController {
billingService;
paymobProvider;
binanceProvider;
logger = new common_1.Logger(BillingController_1.name);
constructor(billingService, paymobProvider, binanceProvider) {
this.billingService = billingService;
this.paymobProvider = paymobProvider;
this.binanceProvider = binanceProvider;
}
async getSubscription(req) {
return this.billingService.getSubscription(req.tenant.id);
}
async checkout(req, body) {
const tenantId = req.tenant.id;
const plan = body.plan;
const pricing = {
'STARTER': 29,
'PRO': 89,
};
const amount = pricing[plan] || 0;
if (amount === 0 && plan !== 'FREE') {
throw new common_1.BadRequestException('Invalid plan or price not configured');
}
if (body.provider === transaction_entity_1.PaymentProvider.PAYMOB) {
const { paymentKey, orderId } = await this.paymobProvider.createPaymentKey(tenantId, amount, plan);
const iframeId = this.billingService.getIframeId();
const checkoutUrl = `https://accept.paymob.com/api/acceptance/iframes/${iframeId}?payment_token=${paymentKey}`;
return { checkoutUrl, orderId };
}
if (body.provider === transaction_entity_1.PaymentProvider.BINANCE) {
return this.binanceProvider.createOrder(tenantId, amount, plan);
}
throw new common_1.BadRequestException('Invalid payment provider');
}
async handlePaymobCallback(query, req) {
this.logger.log(`🔄 PayMob Redirect Callback: ID ${query.id}, Success: ${query.success}`);
if (query.success === 'true' && query.id) {
try {
const txDetails = await this.paymobProvider.getTransactionDetails(query.id);
if (txDetails && txDetails.success === true) {
const extraDesc = txDetails.order?.shipping_data?.extra_description || "";
const [tenantId, plan] = extraDesc.split('|');
if (tenantId) {
await this.billingService.processSuccessfulPayment(query.id.toString(), transaction_entity_1.PaymentProvider.PAYMOB, txDetails.amount_cents / 100, { tenantId, plan: plan || 'PRO' });
this.logger.log(`🚀 Immediate Activation triggered via Callback for Tenant ${tenantId}`);
}
}
}
catch (e) {
this.logger.error(`Failed during immediate activation fallback: ${e.message}`);
}
}
const targetUrl = `https://map-dashboard.intaleqapp.com/dashboard.html#billing?payment_status=${query.success === 'true' ? 'success' : 'failed'}&id=${query.id}`;
return `
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Intaleq Maps | تمت العملية بنجاح</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;700;900&display=swap" rel="stylesheet">
<style>
body { font-family: 'Cairo', sans-serif; background: #050505; color: white; overflow: hidden; }
.pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .7; transform: scale(0.95); } }
</style>
</head>
<body class="flex items-center justify-center h-screen">
<div class="text-center space-y-8 max-w-md p-10 bg-white shadow-2xl rounded-[3rem] border border-white/10 relative overflow-hidden">
<!-- Animated Background Glow -->
<div class="absolute -top-20 -left-20 w-40 h-40 bg-blue-600/20 blur-[80px] rounded-full"></div>
<div class="absolute -bottom-20 -right-20 w-40 h-40 bg-emerald-600/20 blur-[80px] rounded-full"></div>
<div class="relative z-10">
<div class="w-24 h-24 bg-emerald-500 rounded-full mx-auto flex items-center justify-center shadow-[0_0_50px_rgba(16,185,129,0.4)] pulse mb-8">
<svg class="w-12 h-12 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
</svg>
</div>
<h1 class="text-3xl font-black text-slate-900 mb-4">شكراً لك، حمزة!</h1>
<p class="text-slate-500 font-bold mb-8">تم تفعيل خطة <span class="text-blue-600">PRO</span> بنجاح. يتم الآن توجيهك إلى لوحة التحكم...</p>
<div class="flex items-center justify-center gap-2">
<div class="w-2 h-2 bg-blue-600 rounded-full animate-bounce"></div>
<div class="w-2 h-2 bg-blue-600 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
<div class="w-2 h-2 bg-blue-600 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
</div>
</div>
<script>
setTimeout(() => {
window.location.href = "${targetUrl}";
}, 2500);
</script>
</div>
</body>
</html>
`;
}
async handlePaymobWebhook(body, hmac) {
this.logger.log(`📥 PayMob Webhook Received: Transaction ID ${body.obj?.id}`);
if (!this.paymobProvider.verifyHmac(body.obj, hmac)) {
this.logger.error('❌ PayMob HMAC Verification Failed');
throw new common_1.BadRequestException('Invalid signature');
}
if (body.obj.success === true) {
const extraDesc = body.obj.order?.shipping_data?.extra_description || "";
const [tenantId, plan] = extraDesc.split('|');
if (!tenantId) {
this.logger.error(`❌ PayMob Webhook failed: No tenantId in metadata. Raw: ${extraDesc}`);
return { status: 'error', message: 'No tenantId found' };
}
await this.billingService.processSuccessfulPayment(body.obj.id.toString(), transaction_entity_1.PaymentProvider.PAYMOB, body.obj.amount_cents / 100, {
tenantId,
plan: plan || 'PRO'
});
}
return { status: 'success' };
}
async handleBinanceWebhook(body, req) {
const timestamp = req.headers['binancepay-timestamp'];
const nonce = req.headers['binancepay-nonce'];
const signature = req.headers['binancepay-signature'];
this.logger.log(`📥 Binance Webhook Received: Transaction ID ${body.bizId || 'UNKNOWN'}`);
if (!timestamp || !nonce || !signature) {
this.logger.error('❌ Binance Webhook: Missing required headers');
throw new common_1.BadRequestException('Missing headers');
}
if (!this.binanceProvider.verifySignature(timestamp, nonce, signature, body)) {
this.logger.error('❌ Binance Signature Verification Failed');
throw new common_1.BadRequestException('Invalid signature');
}
if (body.bizStatus === 'PAY_SUCCESS') {
try {
let dataObj = {};
if (body.data) {
dataObj = JSON.parse(body.data);
}
let tenantId = '';
let plan = 'PRO';
if (dataObj.passThroughInfo) {
const passThrough = JSON.parse(dataObj.passThroughInfo);
tenantId = passThrough.tenantId;
plan = passThrough.plan;
}
if (!tenantId) {
this.logger.error(`❌ Binance Webhook failed: No tenantId found. Body: ${JSON.stringify(body)}`);
return { returnCode: 'FAIL', returnMessage: 'No tenantId found' };
}
const externalTxId = dataObj.merchantTradeNo || body.bizId?.toString();
await this.billingService.processSuccessfulPayment(externalTxId, transaction_entity_1.PaymentProvider.BINANCE, 0, { tenantId, plan });
this.logger.log(`✅ Binance Payment Processed Successfully for Tenant ${tenantId}`);
}
catch (err) {
this.logger.error(`❌ Failed to process Binance payment: ${err.message}`);
return { returnCode: 'FAIL', returnMessage: err.message };
}
}
return {
returnCode: 'SUCCESS',
returnMessage: null,
};
}
async getInvoices(req) {
return this.billingService.getTransactionHistory(req.tenant.id);
}
};
exports.BillingController = BillingController;
__decorate([
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Get)('subscription'),
(0, swagger_1.ApiOperation)({ summary: 'Get current subscription details' }),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], BillingController.prototype, "getSubscription", null);
__decorate([
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Post)('checkout'),
(0, swagger_1.ApiOperation)({ summary: 'Initialize a checkout session' }),
__param(0, (0, common_1.Req)()),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], BillingController.prototype, "checkout", null);
__decorate([
(0, common_1.Get)('callback/paymob'),
(0, swagger_1.ApiOperation)({ summary: 'PayMob Redirection Handler' }),
__param(0, (0, common_1.Query)()),
__param(1, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], BillingController.prototype, "handlePaymobCallback", null);
__decorate([
(0, common_1.Post)('webhooks/paymob'),
(0, swagger_1.ApiOperation)({ summary: 'PayMob Payment Webhook' }),
__param(0, (0, common_1.Body)()),
__param(1, (0, common_1.Query)('hmac')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", Promise)
], BillingController.prototype, "handlePaymobWebhook", null);
__decorate([
(0, common_1.Post)('webhooks/binance'),
(0, swagger_1.ApiOperation)({ summary: 'Binance Payment Webhook' }),
__param(0, (0, common_1.Body)()),
__param(1, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], BillingController.prototype, "handleBinanceWebhook", null);
__decorate([
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Get)('invoices'),
(0, swagger_1.ApiOperation)({ summary: 'Get payment history' }),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], BillingController.prototype, "getInvoices", null);
exports.BillingController = BillingController = BillingController_1 = __decorate([
(0, swagger_1.ApiTags)('billing'),
(0, common_1.Controller)('billing'),
__metadata("design:paramtypes", [billing_service_1.BillingService,
paymob_provider_1.PayMobProvider,
binance_provider_1.BinanceProvider])
], BillingController);
//# sourceMappingURL=billing.controller.js.map
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
export declare class BillingModule {
}
-32
View File
@@ -1,32 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BillingModule = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const subscription_entity_1 = require("./entities/subscription.entity");
const transaction_entity_1 = require("./entities/transaction.entity");
const billing_service_1 = require("./billing.service");
const billing_controller_1 = require("./billing.controller");
const paymob_provider_1 = require("./providers/paymob.provider");
const binance_provider_1 = require("./providers/binance.provider");
const tenant_entity_1 = require("../auth/entities/tenant.entity");
let BillingModule = class BillingModule {
};
exports.BillingModule = BillingModule;
exports.BillingModule = BillingModule = __decorate([
(0, common_1.Module)({
imports: [
typeorm_1.TypeOrmModule.forFeature([subscription_entity_1.Subscription, transaction_entity_1.Transaction, tenant_entity_1.Tenant]),
],
providers: [billing_service_1.BillingService, paymob_provider_1.PayMobProvider, binance_provider_1.BinanceProvider],
controllers: [billing_controller_1.BillingController],
exports: [billing_service_1.BillingService],
})
], BillingModule);
//# sourceMappingURL=billing.module.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"billing.module.js","sourceRoot":"","sources":["../../src/billing/billing.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,6CAAgD;AAChD,wEAA8D;AAC9D,sEAA4D;AAC5D,uDAAmD;AACnD,6DAAyD;AACzD,iEAA6D;AAC7D,mEAA+D;AAC/D,kEAAwD;AAUjD,IAAM,aAAa,GAAnB,MAAM,aAAa;CAAG,CAAA;AAAhB,sCAAa;wBAAb,aAAa;IARzB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,kCAAY,EAAE,gCAAW,EAAE,sBAAM,CAAC,CAAC;SAC9D;QACD,SAAS,EAAE,CAAC,gCAAc,EAAE,gCAAc,EAAE,kCAAe,CAAC;QAC5D,WAAW,EAAE,CAAC,sCAAiB,CAAC;QAChC,OAAO,EAAE,CAAC,gCAAc,CAAC;KAC1B,CAAC;GACW,aAAa,CAAG"}
-20
View File
@@ -1,20 +0,0 @@
import { ConfigService } from '@nestjs/config';
import { Repository } from 'typeorm';
import { Subscription } from './entities/subscription.entity';
import { Transaction, PaymentProvider } from './entities/transaction.entity';
import { Tenant } from '../auth/entities/tenant.entity';
import { MailService } from '../common/mail.service';
export declare class BillingService {
private subscriptionRepository;
private transactionRepository;
private tenantRepository;
private configService;
private mailService;
private readonly logger;
constructor(subscriptionRepository: Repository<Subscription>, transactionRepository: Repository<Transaction>, tenantRepository: Repository<Tenant>, configService: ConfigService, mailService: MailService);
getIframeId(): string;
getSubscription(tenantId: string): Promise<Subscription>;
processSuccessfulPayment(externalTxId: string, provider: PaymentProvider, amount: number, metadata: any): Promise<void>;
private upgradeTenantPlan;
getTransactionHistory(tenantId: string): Promise<Transaction[]>;
}
-131
View File
@@ -1,131 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var BillingService_1;
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BillingService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const subscription_entity_1 = require("./entities/subscription.entity");
const transaction_entity_1 = require("./entities/transaction.entity");
const tenant_entity_1 = require("../auth/entities/tenant.entity");
const mail_service_1 = require("../common/mail.service");
let BillingService = BillingService_1 = class BillingService {
subscriptionRepository;
transactionRepository;
tenantRepository;
configService;
mailService;
logger = new common_1.Logger(BillingService_1.name);
constructor(subscriptionRepository, transactionRepository, tenantRepository, configService, mailService) {
this.subscriptionRepository = subscriptionRepository;
this.transactionRepository = transactionRepository;
this.tenantRepository = tenantRepository;
this.configService = configService;
this.mailService = mailService;
}
getIframeId() {
return this.configService.get('PAYMOB_IFRAME_ID', '837992');
}
async getSubscription(tenantId) {
let sub = await this.subscriptionRepository.findOne({ where: { tenantId } });
if (!sub) {
sub = await this.subscriptionRepository.save({
tenantId,
plan: 'FREE',
monthlyRequestLimit: 5000,
status: subscription_entity_1.SubscriptionStatus.ACTIVE,
});
}
return sub;
}
async processSuccessfulPayment(externalTxId, provider, amount, metadata) {
let txn = await this.transactionRepository.findOne({ where: { externalTransactionId: externalTxId } });
if (txn && txn.status === transaction_entity_1.PaymentStatus.SUCCESS) {
this.logger.warn(`Transaction ${externalTxId} already processed.`);
return;
}
if (!txn) {
const tenantId = metadata.tenantId;
if (!tenantId)
throw new common_1.BadRequestException('No tenantId found in payment metadata');
txn = await this.transactionRepository.save({
tenantId,
externalTransactionId: externalTxId,
amount,
provider,
status: transaction_entity_1.PaymentStatus.SUCCESS,
metadata,
});
}
else {
txn.status = transaction_entity_1.PaymentStatus.SUCCESS;
txn.metadata = { ...txn.metadata, ...metadata };
await this.transactionRepository.save(txn);
}
const tenantId = txn.tenantId;
const plan = metadata.plan || 'PRO';
await this.upgradeTenantPlan(tenantId, plan);
try {
const tenant = await this.tenantRepository.findOne({ where: { id: tenantId } });
if (tenant && tenant.email) {
await this.mailService.sendInvoiceEmail(tenant.email, {
tenantName: tenant.name,
plan: plan,
amount: `${amount} EGP`,
transactionId: externalTxId,
});
}
}
catch (err) {
this.logger.error(`Failed to send invoice email for tx ${externalTxId}: ${err.message}`);
}
this.logger.log(`✅ Tenant ${tenantId} upgraded to ${plan} after payment ${externalTxId}`);
}
async upgradeTenantPlan(tenantId, plan) {
await this.tenantRepository.update(tenantId, { plan });
const limits = {
[tenant_entity_1.TenantPlan.FREE]: 5000,
[tenant_entity_1.TenantPlan.STARTER]: 25000,
[tenant_entity_1.TenantPlan.PRO]: 100000,
[tenant_entity_1.TenantPlan.ENTERPRISE]: 500000,
};
const sub = await this.getSubscription(tenantId);
sub.plan = plan;
sub.monthlyRequestLimit = limits[plan] || 8000;
sub.status = subscription_entity_1.SubscriptionStatus.ACTIVE;
sub.currentPeriodStart = new Date();
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
sub.currentPeriodEnd = nextMonth;
await this.subscriptionRepository.save(sub);
}
async getTransactionHistory(tenantId) {
return this.transactionRepository.find({
where: { tenantId },
order: { createdAt: 'DESC' },
});
}
};
exports.BillingService = BillingService;
exports.BillingService = BillingService = BillingService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(subscription_entity_1.Subscription)),
__param(1, (0, typeorm_1.InjectRepository)(transaction_entity_1.Transaction)),
__param(2, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, typeof (_c = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _c : Object, typeof (_d = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _d : Object, mail_service_1.MailService])
], BillingService);
//# sourceMappingURL=billing.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"billing.service.js","sourceRoot":"","sources":["../../src/billing/billing.service.ts"],"names":[],"mappings":"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"}
-18
View File
@@ -1,18 +0,0 @@
export declare enum SubscriptionStatus {
ACTIVE = "ACTIVE",
PAST_DUE = "PAST_DUE",
CANCELED = "CANCELED",
TRIALING = "TRIALING"
}
export declare class Subscription {
id: string;
tenantId: string;
plan: string;
status: SubscriptionStatus;
monthlyRequestLimit: number;
currentPeriodStart: Date;
currentPeriodEnd: Date;
canceledAt: Date;
createdAt: Date;
updatedAt: Date;
}
-82
View File
@@ -1,82 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Subscription = exports.SubscriptionStatus = void 0;
const typeorm_1 = require("typeorm");
var SubscriptionStatus;
(function (SubscriptionStatus) {
SubscriptionStatus["ACTIVE"] = "ACTIVE";
SubscriptionStatus["PAST_DUE"] = "PAST_DUE";
SubscriptionStatus["CANCELED"] = "CANCELED";
SubscriptionStatus["TRIALING"] = "TRIALING";
})(SubscriptionStatus || (exports.SubscriptionStatus = SubscriptionStatus = {}));
let Subscription = class Subscription {
id;
tenantId;
plan;
status;
monthlyRequestLimit;
currentPeriodStart;
currentPeriodEnd;
canceledAt;
createdAt;
updatedAt;
};
exports.Subscription = Subscription;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
__metadata("design:type", String)
], Subscription.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Index)(),
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], Subscription.prototype, "tenantId", void 0);
__decorate([
(0, typeorm_1.Column)({ default: 'FREE' }),
__metadata("design:type", String)
], Subscription.prototype, "plan", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: SubscriptionStatus,
default: SubscriptionStatus.ACTIVE,
}),
__metadata("design:type", String)
], Subscription.prototype, "status", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'int', default: 8000 }),
__metadata("design:type", Number)
], Subscription.prototype, "monthlyRequestLimit", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'timestamp', nullable: true }),
__metadata("design:type", Date)
], Subscription.prototype, "currentPeriodStart", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'timestamp', nullable: true }),
__metadata("design:type", Date)
], Subscription.prototype, "currentPeriodEnd", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'timestamp', nullable: true }),
__metadata("design:type", Date)
], Subscription.prototype, "canceledAt", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
__metadata("design:type", Date)
], Subscription.prototype, "createdAt", void 0);
__decorate([
(0, typeorm_1.UpdateDateColumn)(),
__metadata("design:type", Date)
], Subscription.prototype, "updatedAt", void 0);
exports.Subscription = Subscription = __decorate([
(0, typeorm_1.Entity)('subscriptions')
], Subscription);
//# sourceMappingURL=subscription.entity.js.map
@@ -1 +0,0 @@
{"version":3,"file":"subscription.entity.js","sourceRoot":"","sources":["../../../src/billing/entities/subscription.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAQiB;AAGjB,IAAY,kBAKX;AALD,WAAY,kBAAkB;IAC5B,uCAAiB,CAAA;IACjB,2CAAqB,CAAA;IACrB,2CAAqB,CAAA;IACrB,2CAAqB,CAAA;AACvB,CAAC,EALW,kBAAkB,kCAAlB,kBAAkB,QAK7B;AAGM,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEvB,EAAE,CAAS;IAIX,QAAQ,CAAS;IAGjB,IAAI,CAAS;IAOb,MAAM,CAAqB;IAG3B,mBAAmB,CAAS;IAG5B,kBAAkB,CAAO;IAGzB,gBAAgB,CAAO;IAGvB,UAAU,CAAO;IAGjB,SAAS,CAAO;IAGhB,SAAS,CAAO;CACjB,CAAA;AAnCY,oCAAY;AAEvB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;wCACpB;AAIX;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,gBAAM,GAAE;;8CACQ;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;;0CACf;AAOb;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,kBAAkB,CAAC,MAAM;KACnC,CAAC;;4CACyB;AAG3B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;yDACX;AAG5B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BAC1B,IAAI;wDAAC;AAGzB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BAC5B,IAAI;sDAAC;AAGvB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BAClC,IAAI;gDAAC;AAGjB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;+CAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;+CAAC;uBAlCL,YAAY;IADxB,IAAA,gBAAM,EAAC,eAAe,CAAC;GACX,YAAY,CAmCxB"}
-23
View File
@@ -1,23 +0,0 @@
export declare enum PaymentStatus {
PENDING = "PENDING",
SUCCESS = "SUCCESS",
FAILED = "FAILED",
REFUNDED = "REFUNDED"
}
export declare enum PaymentProvider {
PAYMOB = "PAYMOB",
BINANCE = "BINANCE",
MANUAL = "MANUAL"
}
export declare class Transaction {
id: string;
tenantId: string;
subscriptionId: string;
amount: number;
currency: string;
provider: PaymentProvider;
externalTransactionId: string;
status: PaymentStatus;
metadata: Record<string, any>;
createdAt: Date;
}
-92
View File
@@ -1,92 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Transaction = exports.PaymentProvider = exports.PaymentStatus = void 0;
const typeorm_1 = require("typeorm");
var PaymentStatus;
(function (PaymentStatus) {
PaymentStatus["PENDING"] = "PENDING";
PaymentStatus["SUCCESS"] = "SUCCESS";
PaymentStatus["FAILED"] = "FAILED";
PaymentStatus["REFUNDED"] = "REFUNDED";
})(PaymentStatus || (exports.PaymentStatus = PaymentStatus = {}));
var PaymentProvider;
(function (PaymentProvider) {
PaymentProvider["PAYMOB"] = "PAYMOB";
PaymentProvider["BINANCE"] = "BINANCE";
PaymentProvider["MANUAL"] = "MANUAL";
})(PaymentProvider || (exports.PaymentProvider = PaymentProvider = {}));
let Transaction = class Transaction {
id;
tenantId;
subscriptionId;
amount;
currency;
provider;
externalTransactionId;
status;
metadata;
createdAt;
};
exports.Transaction = Transaction;
__decorate([
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
__metadata("design:type", String)
], Transaction.prototype, "id", void 0);
__decorate([
(0, typeorm_1.Index)(),
(0, typeorm_1.Column)(),
__metadata("design:type", String)
], Transaction.prototype, "tenantId", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], Transaction.prototype, "subscriptionId", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'decimal', precision: 10, scale: 2 }),
__metadata("design:type", Number)
], Transaction.prototype, "amount", void 0);
__decorate([
(0, typeorm_1.Column)({ default: 'USD' }),
__metadata("design:type", String)
], Transaction.prototype, "currency", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: PaymentProvider,
}),
__metadata("design:type", String)
], Transaction.prototype, "provider", void 0);
__decorate([
(0, typeorm_1.Index)(),
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], Transaction.prototype, "externalTransactionId", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: PaymentStatus,
default: PaymentStatus.PENDING,
}),
__metadata("design:type", String)
], Transaction.prototype, "status", void 0);
__decorate([
(0, typeorm_1.Column)({ type: 'jsonb', nullable: true }),
__metadata("design:type", Object)
], Transaction.prototype, "metadata", void 0);
__decorate([
(0, typeorm_1.CreateDateColumn)(),
__metadata("design:type", Date)
], Transaction.prototype, "createdAt", void 0);
exports.Transaction = Transaction = __decorate([
(0, typeorm_1.Entity)('transactions')
], Transaction);
//# sourceMappingURL=transaction.entity.js.map
@@ -1 +0,0 @@
{"version":3,"file":"transaction.entity.js","sourceRoot":"","sources":["../../../src/billing/entities/transaction.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAMiB;AAEjB,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,oCAAmB,CAAA;IACnB,kCAAiB,CAAA;IACjB,sCAAqB,CAAA;AACvB,CAAC,EALW,aAAa,6BAAb,aAAa,QAKxB;AAED,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,oCAAiB,CAAA;IACjB,sCAAmB,CAAA;IACnB,oCAAiB,CAAA;AACnB,CAAC,EAJW,eAAe,+BAAf,eAAe,QAI1B;AAGM,IAAM,WAAW,GAAjB,MAAM,WAAW;IAEtB,EAAE,CAAS;IAIX,QAAQ,CAAS;IAGjB,cAAc,CAAS;IAGvB,MAAM,CAAS;IAGf,QAAQ,CAAS;IAMjB,QAAQ,CAAkB;IAI1B,qBAAqB,CAAS;IAO9B,MAAM,CAAgB;IAGtB,QAAQ,CAAsB;IAG9B,SAAS,CAAO;CACjB,CAAA;AAvCY,kCAAW;AAEtB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;uCACpB;AAIX;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,gBAAM,GAAE;;6CACQ;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mDACJ;AAGvB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;;2CACtC;AAGf;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;6CACV;AAMjB;IAJC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,eAAe;KACtB,CAAC;;6CACwB;AAI1B;IAFC,IAAA,eAAK,GAAE;IACP,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0DACG;AAO9B;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,aAAa,CAAC,OAAO;KAC/B,CAAC;;2CACoB;AAGtB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6CACZ;AAG9B;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;8CAAC;sBAtCL,WAAW;IADvB,IAAA,gBAAM,EAAC,cAAc,CAAC;GACV,WAAW,CAuCvB"}
-16
View File
@@ -1,16 +0,0 @@
import { ConfigService } from '@nestjs/config';
export declare class BinanceProvider {
private configService;
private readonly logger;
private readonly apiUrl;
constructor(configService: ConfigService);
private get apiKey();
private get secretKey();
private generateSignature;
createOrder(tenantId: string, amount: number, plan: string): Promise<{
checkoutUrl: any;
orderId: string;
prepayId: any;
}>;
verifySignature(timestamp: string, nonce: string, signature: string, payloadBody: any): boolean;
}
-150
View File
@@ -1,150 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var BinanceProvider_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BinanceProvider = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const axios_1 = __importDefault(require("axios"));
const crypto = __importStar(require("crypto"));
let BinanceProvider = BinanceProvider_1 = class BinanceProvider {
configService;
logger = new common_1.Logger(BinanceProvider_1.name);
apiUrl = 'https://bpay.binanceapi.com/binancepay/openapi/v2/order';
constructor(configService) {
this.configService = configService;
}
get apiKey() {
return this.configService.get('BINANCE_PAY_API_KEY') || '';
}
get secretKey() {
return this.configService.get('BINANCE_PAY_SECRET_KEY') || '';
}
generateSignature(timestamp, nonce, body) {
const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(body) + '\n';
return crypto
.createHmac('sha512', this.secretKey)
.update(payload)
.digest('hex')
.toUpperCase();
}
async createOrder(tenantId, amount, plan) {
this.logger.log(`Creating Binance Pay order for ${tenantId} - ${plan}`);
if (!this.apiKey || !this.secretKey) {
this.logger.error('Binance Pay API keys are not configured');
throw new common_1.InternalServerErrorException('Payment provider is not configured properly');
}
const nonce = crypto.randomBytes(16).toString('hex');
const timestamp = Date.now().toString();
const merchantTradeNo = `txn_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
const body = {
env: {
terminalType: 'WEB',
},
merchantTradeNo: merchantTradeNo,
orderAmount: amount,
currency: 'USDT',
goods: {
goodsType: '01',
goodsCategory: 'Z000',
referenceGoodsId: plan,
goodsName: `${plan} Plan Subscription`,
goodsDetail: `Subscription to ${plan} plan for Maps SaaS`,
},
passThroughInfo: JSON.stringify({ tenantId, plan }),
returnUrl: `https://map-dashboard.intaleqapp.com/dashboard.html#billing`,
};
const signature = this.generateSignature(timestamp, nonce, body);
try {
const response = await axios_1.default.post(this.apiUrl, body, {
headers: {
'Content-Type': 'application/json',
'BinancePay-Timestamp': timestamp,
'BinancePay-Nonce': nonce,
'BinancePay-Certificate-SN': this.apiKey,
'BinancePay-Signature': signature,
},
});
if (response.data && response.data.status === 'SUCCESS') {
return {
checkoutUrl: response.data.data.checkoutUrl,
orderId: merchantTradeNo,
prepayId: response.data.data.prepayId,
};
}
else {
this.logger.error(`Binance API Error: ${JSON.stringify(response.data)}`);
throw new common_1.InternalServerErrorException('Failed to create Binance order');
}
}
catch (error) {
this.logger.error(`Error communicating with Binance API: ${error.message}`);
throw new common_1.InternalServerErrorException('Failed to communicate with payment provider');
}
}
verifySignature(timestamp, nonce, signature, payloadBody) {
const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(payloadBody) + '\n';
const expectedSignature = crypto
.createHmac('sha512', this.secretKey)
.update(payload)
.digest('hex')
.toUpperCase();
try {
return crypto.timingSafeEqual(Buffer.from(signature || ''), Buffer.from(expectedSignature));
}
catch (e) {
return false;
}
}
};
exports.BinanceProvider = BinanceProvider;
exports.BinanceProvider = BinanceProvider = BinanceProvider_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], BinanceProvider);
//# sourceMappingURL=binance.provider.js.map
@@ -1 +0,0 @@
{"version":3,"file":"binance.provider.js","sourceRoot":"","sources":["../../../src/billing/providers/binance.provider.ts"],"names":[],"mappings":"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"}
-14
View File
@@ -1,14 +0,0 @@
import { ConfigService } from '@nestjs/config';
export declare class PayMobProvider {
private configService;
private readonly logger;
private readonly baseUrl;
constructor(configService: ConfigService);
createPaymentKey(tenantId: string, amount: number, plan: string): Promise<{
paymentKey: string;
orderId: string;
}>;
getTransactionDetails(transactionId: string): Promise<any>;
private getAuthToken;
verifyHmac(payload: any, hmac: string): boolean;
}
-179
View File
@@ -1,179 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var PayMobProvider_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PayMobProvider = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const axios_1 = __importDefault(require("axios"));
const crypto = __importStar(require("crypto"));
let PayMobProvider = PayMobProvider_1 = class PayMobProvider {
configService;
logger = new common_1.Logger(PayMobProvider_1.name);
baseUrl = 'https://accept.paymob.com/api';
constructor(configService) {
this.configService = configService;
}
async createPaymentKey(tenantId, amount, plan) {
try {
const authToken = await this.getAuthToken();
const orderRes = await axios_1.default.post(`${this.baseUrl}/ecommerce/orders`, {
auth_token: authToken,
delivery_needed: "false",
amount_cents: amount * 50 * 100,
currency: "EGP",
items: [{
name: `${plan} Subscription`,
amount_cents: amount * 50 * 100,
description: `Intaleq Maps ${plan} Plan`
}],
shipping_data: {
first_name: "Intaleq",
last_name: "User",
email: "user@intaleq.com",
phone_number: "+201000000000",
extra_description: `${tenantId}|${plan}`
}
});
const orderId = orderRes.data.id;
const keyRes = await axios_1.default.post(`${this.baseUrl}/acceptance/payment_keys`, {
auth_token: authToken,
amount_cents: amount * 50 * 100,
expiration: 3600,
order_id: orderId,
billing_data: {
apartment: "NA",
email: "user@intaleq.com",
floor: "NA",
first_name: "Intaleq",
street: "NA",
building: "NA",
phone_number: "+201000000000",
shipping_method: "NA",
postal_code: "NA",
city: "NA",
country: "NA",
last_name: "User",
state: "NA"
},
currency: "EGP",
integration_id: this.configService.get('PAYMOB_INTEGRATION_ID') || 4556055,
lock_order_when_paid: "false"
});
return {
paymentKey: keyRes.data.token,
orderId: orderId.toString()
};
}
catch (error) {
this.logger.error(`PayMob Payment Key failed: ${error.response?.data ? JSON.stringify(error.response.data) : error.message}`);
throw new common_1.BadRequestException('Failed to initialize PayMob payment');
}
}
async getTransactionDetails(transactionId) {
try {
const authToken = await this.getAuthToken();
const res = await axios_1.default.get(`${this.baseUrl}/acceptance/transactions/${transactionId}`, {
headers: { Authorization: `Bearer ${authToken}` }
});
return res.data;
}
catch (error) {
this.logger.error(`Failed to fetch PayMob transaction ${transactionId}: ${error.message}`);
return null;
}
}
async getAuthToken() {
const authRes = await axios_1.default.post(`${this.baseUrl}/auth/tokens`, {
api_key: this.configService.get('PAYMOB_API_KEY'),
});
return authRes.data.token;
}
verifyHmac(payload, hmac) {
const secret = this.configService.get('PAYMOB_HMAC_SECRET');
if (!secret)
return false;
const { amount_cents, created_at, currency, error_occured, has_parent_transaction, id, integration_id, is_3d_secure, is_auth, is_capture, is_refunded, is_standalone_payment, is_voided, order, owner, pending, success } = payload;
const source_pan = payload.source_data?.pan || "";
const source_sub_type = payload.source_data?.sub_type || "";
const source_type = payload.source_data?.type || "";
const data = [
amount_cents,
created_at,
currency,
error_occured,
has_parent_transaction,
id,
integration_id,
is_3d_secure,
is_auth,
is_capture,
is_refunded,
is_standalone_payment,
is_voided,
order.id || order,
owner,
pending,
source_pan,
source_sub_type,
source_type,
success
].join("");
const hash = crypto
.createHmac('sha512', secret)
.update(data)
.digest('hex');
return hash === hmac;
}
};
exports.PayMobProvider = PayMobProvider;
exports.PayMobProvider = PayMobProvider = PayMobProvider_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], PayMobProvider);
//# sourceMappingURL=paymob.provider.js.map
@@ -1 +0,0 @@
{"version":3,"file":"paymob.provider.js","sourceRoot":"","sources":["../../../src/billing/providers/paymob.provider.ts"],"names":[],"mappings":"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"}
-9
View File
@@ -1,9 +0,0 @@
import { ConfigService } from '@nestjs/config';
export declare class CacheService {
private configService;
private client;
constructor(configService: ConfigService);
set(key: string, value: any, ttlSeconds?: number): Promise<void>;
get(key: string): Promise<any>;
del(key: string): Promise<void>;
}
-44
View File
@@ -1,44 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheService = void 0;
const common_1 = require("@nestjs/common");
const redis_1 = require("redis");
const config_1 = require("@nestjs/config");
let CacheService = class CacheService {
configService;
client;
constructor(configService) {
this.configService = configService;
const redisUrl = this.configService.get('REDIS_URL', 'redis://redis:6379');
this.client = (0, redis_1.createClient)({ url: redisUrl });
this.client.connect().catch(console.error);
}
async set(key, value, ttlSeconds = 3600) {
await this.client.set(key, JSON.stringify(value), {
EX: ttlSeconds,
});
}
async get(key) {
const data = await this.client.get(key);
return data ? JSON.parse(data) : null;
}
async del(key) {
await this.client.del(key);
}
};
exports.CacheService = CacheService;
exports.CacheService = CacheService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], CacheService);
//# sourceMappingURL=cache.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"cache.service.js","sourceRoot":"","sources":["../../src/common/cache.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAoD;AACpD,iCAAqC;AACrC,2CAA+C;AAGxC,IAAM,YAAY,GAAlB,MAAM,YAAY;IAGH;IAFZ,MAAM,CAAC;IAEf,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC;QAC3E,IAAI,CAAC,MAAM,GAAG,IAAA,oBAAY,EAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,aAAqB,IAAI;QAG1D,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;YAChD,EAAE,EAAE,UAAU;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QAGnB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF,CAAA;AA3BY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;yDAIwB,sBAAa,oBAAb,sBAAa;GAHrC,YAAY,CA2BxB"}
-8
View File
@@ -1,8 +0,0 @@
export declare const JORDAN_BOUNDS: {
minLat: number;
maxLat: number;
minLng: number;
maxLng: number;
};
export declare function isWithinJordan(lat: number, lng: number): boolean;
export declare function mockMapMatch(lat: number, lng: number): string;
-21
View File
@@ -1,21 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JORDAN_BOUNDS = void 0;
exports.isWithinJordan = isWithinJordan;
exports.mockMapMatch = mockMapMatch;
exports.JORDAN_BOUNDS = {
minLat: 29.1,
maxLat: 33.4,
minLng: 34.9,
maxLng: 39.3,
};
function isWithinJordan(lat, lng) {
return (lat >= exports.JORDAN_BOUNDS.minLat &&
lat <= exports.JORDAN_BOUNDS.maxLat &&
lng >= exports.JORDAN_BOUNDS.minLng &&
lng <= exports.JORDAN_BOUNDS.maxLng);
}
function mockMapMatch(lat, lng) {
return `osm_way_${Math.floor(Math.random() * 1000000)}`;
}
//# sourceMappingURL=gis.utils.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"gis.utils.js","sourceRoot":"","sources":["../../src/common/gis.utils.ts"],"names":[],"mappings":";;;AAYA,wCAOC;AAMD,oCAGC;AAvBY,QAAA,aAAa,GAAG;IAC3B,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;CACb,CAAC;AAEF,SAAgB,cAAc,CAAC,GAAW,EAAE,GAAW;IACrD,OAAO,CACL,GAAG,IAAI,qBAAa,CAAC,MAAM;QAC3B,GAAG,IAAI,qBAAa,CAAC,MAAM;QAC3B,GAAG,IAAI,qBAAa,CAAC,MAAM;QAC3B,GAAG,IAAI,qBAAa,CAAC,MAAM,CAC5B,CAAC;AACJ,CAAC;AAMD,SAAgB,YAAY,CAAC,GAAW,EAAE,GAAW;IAEnD,OAAO,WAAW,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;AAC1D,CAAC"}
-4
View File
@@ -1,4 +0,0 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
export declare class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean;
}
-29
View File
@@ -1,29 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminGuard = void 0;
const common_1 = require("@nestjs/common");
const tenant_entity_1 = require("../../auth/entities/tenant.entity");
let AdminGuard = class AdminGuard {
canActivate(context) {
const request = context.switchToHttp().getRequest();
const tenant = request['tenant'];
if (!tenant) {
throw new common_1.UnauthorizedException('Tenant not found in request');
}
if (tenant.plan !== tenant_entity_1.TenantPlan.ENTERPRISE) {
throw new common_1.ForbiddenException('Admin access required. Your tenant plan must be ENTERPRISE.');
}
return true;
}
};
exports.AdminGuard = AdminGuard;
exports.AdminGuard = AdminGuard = __decorate([
(0, common_1.Injectable)()
], AdminGuard);
//# sourceMappingURL=admin.guard.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"admin.guard.js","sourceRoot":"","sources":["../../../src/common/guards/admin.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAsH;AACtH,qEAA+D;AAGxD,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,WAAW,CAAC,OAAyB;QACnC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,8BAAqB,CAAC,6BAA6B,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,0BAAU,CAAC,UAAU,EAAE,CAAC;YAC1C,MAAM,IAAI,2BAAkB,CAAC,6DAA6D,CAAC,CAAC;QAC9F,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAfY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAetB"}
-7
View File
@@ -1,7 +0,0 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { AuthService } from '../../auth/auth.service';
export declare class ApiKeyGuard implements CanActivate {
private authService;
constructor(authService: AuthService);
canActivate(context: ExecutionContext): Promise<boolean>;
}
-45
View File
@@ -1,45 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiKeyGuard = void 0;
const common_1 = require("@nestjs/common");
const auth_service_1 = require("../../auth/auth.service");
let ApiKeyGuard = class ApiKeyGuard {
authService;
constructor(authService) {
this.authService = authService;
}
async canActivate(context) {
const request = context.switchToHttp().getRequest();
const apiKeyHeader = request.headers['x-api-key'] || request.query['api_key'];
if (!apiKeyHeader) {
throw new common_1.UnauthorizedException('API Key (x-api-key) is missing');
}
const origin = request.headers['origin'];
const referer = request.headers['referer'];
try {
const { tenant, apiKey, rateLimit } = await this.authService.validateApiKey(apiKeyHeader, origin, referer);
request['tenant'] = tenant;
request['apiKey'] = apiKey;
request['rateLimit'] = rateLimit;
return true;
}
catch (error) {
throw new common_1.UnauthorizedException(error.message || 'Invalid API Key');
}
}
};
exports.ApiKeyGuard = ApiKeyGuard;
exports.ApiKeyGuard = ApiKeyGuard = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [auth_service_1.AuthService])
], ApiKeyGuard);
//# sourceMappingURL=api-key.guard.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"api-key.guard.js","sourceRoot":"","sources":["../../../src/common/guards/api-key.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,0DAAsD;AAG/C,IAAM,WAAW,GAAjB,MAAM,WAAW;IACF;IAApB,YAAoB,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAEhD,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGpD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAE9E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,8BAAqB,CAAC,gCAAgC,CAAC,CAAC;QACpE,CAAC;QAGD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAE3C,IAAI,CAAC;YAEH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CACzE,YAAY,EACZ,MAAM,EACN,OAAO,CACR,CAAC;YAGF,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;YAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;YAC3B,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,CAAC;YAEjC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,8BAAqB,CAAC,KAAK,CAAC,OAAO,IAAI,iBAAiB,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;CACF,CAAA;AAnCY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEsB,0BAAW;GADjC,WAAW,CAmCvB"}
-6
View File
@@ -1,6 +0,0 @@
import { ThrottlerGuard, ThrottlerRequest } from '@nestjs/throttler';
export declare class TenantThrottlerGuard extends ThrottlerGuard {
private readonly logger;
protected getTracker(req: Record<string, any>): Promise<string>;
protected handleRequest(requestProps: ThrottlerRequest): Promise<boolean>;
}
-37
View File
@@ -1,37 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var TenantThrottlerGuard_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TenantThrottlerGuard = void 0;
const common_1 = require("@nestjs/common");
const throttler_1 = require("@nestjs/throttler");
let TenantThrottlerGuard = TenantThrottlerGuard_1 = class TenantThrottlerGuard extends throttler_1.ThrottlerGuard {
logger = new common_1.Logger(TenantThrottlerGuard_1.name);
async getTracker(req) {
const apiKeyId = req['apiKey']?.id || req.ip;
return `throttler:key:${apiKeyId}`;
}
async handleRequest(requestProps) {
const { context, limit, ttl } = requestProps;
const request = context.switchToHttp().getRequest();
const dynamicLimit = request['rateLimit'];
if (dynamicLimit) {
this.logger.debug(`Applying dynamic rate limit: ${dynamicLimit} req/min for key ${request['apiKey']?.id}`);
requestProps.limit = dynamicLimit;
}
else {
this.logger.debug(`Applying default rate limit: ${limit} req/min`);
}
return super.handleRequest(requestProps);
}
};
exports.TenantThrottlerGuard = TenantThrottlerGuard;
exports.TenantThrottlerGuard = TenantThrottlerGuard = TenantThrottlerGuard_1 = __decorate([
(0, common_1.Injectable)()
], TenantThrottlerGuard);
//# sourceMappingURL=rate-limiter.guard.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"rate-limiter.guard.js","sourceRoot":"","sources":["../../../src/common/guards/rate-limiter.guard.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAsE;AACtE,iDAAqE;AAG9D,IAAM,oBAAoB,4BAA1B,MAAM,oBAAqB,SAAQ,0BAAc;IACrC,MAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAEtD,KAAK,CAAC,UAAU,CAAC,GAAwB;QACjD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,iBAAiB,QAAQ,EAAE,CAAC;IACrC,CAAC;IAES,KAAK,CAAC,aAAa,CAC3B,YAA8B;QAE9B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC;QAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGpD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QAE1C,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,YAAY,oBAAoB,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3G,YAAY,CAAC,KAAK,GAAG,YAAY,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,KAAK,UAAU,CAAC,CAAC;QACrE,CAAC;QAED,OAAO,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;CACF,CAAA;AA1BY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;GACA,oBAAoB,CA0BhC"}
-2
View File
@@ -1,2 +0,0 @@
export declare class MailModule {
}
-22
View File
@@ -1,22 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MailModule = void 0;
const common_1 = require("@nestjs/common");
const mail_service_1 = require("./mail.service");
let MailModule = class MailModule {
};
exports.MailModule = MailModule;
exports.MailModule = MailModule = __decorate([
(0, common_1.Global)(),
(0, common_1.Module)({
providers: [mail_service_1.MailService],
exports: [mail_service_1.MailService],
})
], MailModule);
//# sourceMappingURL=mail.module.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"mail.module.js","sourceRoot":"","sources":["../../src/common/mail.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,iDAA6C;AAOtC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IALtB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"}
-13
View File
@@ -1,13 +0,0 @@
import { ConfigService } from '@nestjs/config';
export declare class MailService {
private configService;
private readonly logger;
private transporter;
constructor(configService: ConfigService);
sendInvoiceEmail(to: string, data: {
tenantName: string;
plan: string;
amount: string;
transactionId: string;
}): Promise<boolean>;
}
-122
View File
@@ -1,122 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var MailService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MailService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const nodemailer = __importStar(require("nodemailer"));
let MailService = MailService_1 = class MailService {
configService;
logger = new common_1.Logger(MailService_1.name);
transporter;
constructor(configService) {
this.configService = configService;
const host = this.configService.get('SMTP_HOST');
const port = this.configService.get('SMTP_PORT') || 587;
const user = this.configService.get('SMTP_USER');
const pass = this.configService.get('SMTP_PASS');
if (host && user && pass) {
this.transporter = nodemailer.createTransport({
host,
port,
secure: port === 465,
auth: {
user,
pass,
},
});
this.logger.log(`📧 MailService initialized with host: ${host}`);
}
else {
this.logger.warn('⚠️ SMTP settings not fully configured. MailService will run in mock mode.');
}
}
async sendInvoiceEmail(to, data) {
const from = this.configService.get('SMTP_FROM') || 'support@intaleqapp.com';
const htmlContent = `
<div style="font-family: sans-serif; max-width: 600px; margin: auto; border: 1px solid #eee; padding: 20px; border-radius: 10px;">
<h2 style="color: #2563eb;">Intaleq Maps - Payment Successful</h2>
<p>Hello <b>${data.tenantName}</b>,</p>
<p>Thank you for subscribing to our <b>${data.plan}</b> plan. Your payment has been processed successfully.</p>
<div style="background: #f8fafc; padding: 15px; border-radius: 8px; margin: 20px 0;">
<p style="margin: 5px 0;"><b>Plan:</b> ${data.plan}</p>
<p style="margin: 5px 0;"><b>Amount:</b> ${data.amount}</p>
<p style="margin: 5px 0;"><b>Transaction ID:</b> ${data.transactionId}</p>
<p style="margin: 5px 0;"><b>Date:</b> ${new Date().toLocaleDateString()}</p>
</div>
<p>You can now access all your premium features from your dashboard.</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="font-size: 12px; color: #64748b;">If you have any questions, contact us at ${from}</p>
</div>
`;
if (!this.transporter) {
this.logger.warn(`[Mock Email] To: ${to}, Plan: ${data.plan}, Amount: ${data.amount}`);
return true;
}
try {
await this.transporter.sendMail({
from: `"Intaleq Support" <${from}>`,
to,
subject: `Invoice: ${data.plan} Subscription - Intaleq Maps`,
html: htmlContent,
});
this.logger.log(`✅ Invoice email sent to ${to}`);
return true;
}
catch (error) {
this.logger.error(`❌ Failed to send email: ${error.message}`);
return false;
}
}
};
exports.MailService = MailService;
exports.MailService = MailService = MailService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], MailService);
//# sourceMappingURL=mail.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"mail.service.js","sourceRoot":"","sources":["../../src/common/mail.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,2CAA+C;AAC/C,uDAAyC;AAGlC,IAAM,WAAW,mBAAjB,MAAM,WAAW;IAIF;IAHH,MAAM,GAAG,IAAI,eAAM,CAAC,aAAW,CAAC,IAAI,CAAC,CAAC;IAC/C,WAAW,CAAyB;IAE5C,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,WAAW,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,WAAW,CAAC,IAAI,GAAG,CAAC;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,WAAW,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,WAAW,CAAC,CAAC;QAEzD,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACzB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,eAAe,CAAC;gBAC5C,IAAI;gBACJ,IAAI;gBACJ,MAAM,EAAE,IAAI,KAAK,GAAG;gBACpB,IAAI,EAAE;oBACJ,IAAI;oBACJ,IAAI;iBACL;aACF,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;QAChG,CAAC;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EAAU,EAAE,IAKlC;QACC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,WAAW,CAAC,IAAI,wBAAwB,CAAC;QAErF,MAAM,WAAW,GAAG;;;sBAGF,IAAI,CAAC,UAAU;iDACY,IAAI,CAAC,IAAI;;;mDAGP,IAAI,CAAC,IAAI;qDACP,IAAI,CAAC,MAAM;6DACH,IAAI,CAAC,aAAa;mDAC5B,IAAI,IAAI,EAAE,CAAC,kBAAkB,EAAE;;;;;+FAKa,IAAI;;KAE9F,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,WAAW,IAAI,CAAC,IAAI,aAAa,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACvF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;gBAC9B,IAAI,EAAE,sBAAsB,IAAI,GAAG;gBACnC,EAAE;gBACF,OAAO,EAAE,YAAY,IAAI,CAAC,IAAI,8BAA8B;gBAC5D,IAAI,EAAE,WAAW;aAClB,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF,CAAA;AAxEY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;yDAKwB,sBAAa,oBAAb,sBAAa;GAJrC,WAAW,CAwEvB"}
-2
View File
@@ -1,2 +0,0 @@
export declare class RedisModule {
}
-36
View File
@@ -1,36 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisModule = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const redis_1 = require("redis");
const redis_service_1 = require("./redis.service");
let RedisModule = class RedisModule {
};
exports.RedisModule = RedisModule;
exports.RedisModule = RedisModule = __decorate([
(0, common_1.Global)(),
(0, common_1.Module)({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: async (configService) => {
const url = configService.get('REDIS_URL', 'redis://redis:6379');
const client = (0, redis_1.createClient)({ url });
await client.connect();
return client;
},
inject: [config_1.ConfigService],
},
redis_service_1.RedisService,
],
exports: ['REDIS_CLIENT', redis_service_1.RedisService],
})
], RedisModule);
//# sourceMappingURL=redis.module.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"redis.module.js","sourceRoot":"","sources":["../../src/common/redis.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAgD;AAChD,2CAA+C;AAC/C,iCAAqC;AACrC,mDAA+C;AAwBxC,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IAjBvB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,cAAc;gBACvB,UAAU,EAAE,KAAK,EAAE,aAA4B,EAAE,EAAE;oBACjD,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAS,WAAW,EAAE,oBAAoB,CAAC,CAAC;oBACzE,MAAM,MAAM,GAAG,IAAA,oBAAY,EAAC,EAAE,GAAG,EAAE,CAAC,CAAC;oBACrC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;oBACvB,OAAO,MAAM,CAAC;gBAChB,CAAC;gBACD,MAAM,EAAE,CAAC,sBAAa,CAAC;aACxB;YACD,4BAAY;SACb;QACD,OAAO,EAAE,CAAC,cAAc,EAAE,4BAAY,CAAC;KACxC,CAAC;GACW,WAAW,CAAG"}
-11
View File
@@ -1,11 +0,0 @@
import { OnModuleDestroy } from '@nestjs/common';
import type { RedisClientType } from 'redis';
export declare class RedisService implements OnModuleDestroy {
private readonly redisClient;
constructor(redisClient: RedisClientType);
getClient(): RedisClientType;
set(key: string, value: any, ttlSeconds?: number): Promise<void>;
get<T>(key: string): Promise<T | null>;
del(key: string): Promise<void>;
onModuleDestroy(): Promise<void>;
}
-58
View File
@@ -1,58 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisService = void 0;
const common_1 = require("@nestjs/common");
let RedisService = class RedisService {
redisClient;
constructor(redisClient) {
this.redisClient = redisClient;
}
getClient() {
return this.redisClient;
}
async set(key, value, ttlSeconds) {
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
if (ttlSeconds) {
await this.redisClient.set(key, stringValue, { EX: ttlSeconds });
}
else {
await this.redisClient.set(key, stringValue);
}
}
async get(key) {
const value = await this.redisClient.get(key);
if (!value)
return null;
try {
return JSON.parse(value);
}
catch {
return value;
}
}
async del(key) {
await this.redisClient.del(key);
}
async onModuleDestroy() {
await this.redisClient.quit();
}
};
exports.RedisService = RedisService;
exports.RedisService = RedisService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)('REDIS_CLIENT')),
__metadata("design:paramtypes", [Object])
], RedisService);
//# sourceMappingURL=redis.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"redis.service.js","sourceRoot":"","sources":["../../src/common/redis.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAqE;AAS9D,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEoB;IAD3C,YAC2C,WAA4B;QAA5B,gBAAW,GAAX,WAAW,CAAiB;IACpE,CAAC;IAKJ,SAAS;QACP,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAMD,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,UAAmB;QACpD,MAAM,WAAW,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9E,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAMD,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAqB,CAAC;QAC/B,CAAC;IACH,CAAC;IAMD,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;CACF,CAAA;AAnDY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,cAAc,CAAC,CAAA;;GAFd,YAAY,CAmDxB"}
-17
View File
@@ -1,17 +0,0 @@
import { ConfigService } from '@nestjs/config';
export declare class TelegramService {
private configService;
private readonly logger;
private readonly botToken;
private readonly chatId;
constructor(configService: ConfigService);
sendMessage(text: string): Promise<boolean>;
sendIntelligenceReport(stats: {
syncResult: number;
updatedSegments: number;
discoveredRoads: number;
totalPoints: number;
days: number;
timeProfiles?: number;
}): Promise<boolean>;
}
-76
View File
@@ -1,76 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var TelegramService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelegramService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const axios_1 = __importDefault(require("axios"));
let TelegramService = TelegramService_1 = class TelegramService {
configService;
logger = new common_1.Logger(TelegramService_1.name);
botToken;
chatId;
constructor(configService) {
this.configService = configService;
this.botToken = this.configService.get('TELEGRAM_BOT_TOKEN') || '';
this.chatId = this.configService.get('TELEGRAM_CHAT_ID') || '';
}
async sendMessage(text) {
if (!this.botToken || !this.chatId || this.botToken.includes('YOUR_BOT_TOKEN')) {
this.logger.warn('Telegram Bot Token or Chat ID not configured. Skipping notification.');
return false;
}
try {
this.logger.log('Sending report to Telegram...');
const url = `https://api.telegram.org/bot${this.botToken}/sendMessage`;
await axios_1.default.post(url, {
chat_id: this.chatId,
text: text,
parse_mode: 'HTML',
});
this.logger.log('✅ Telegram notification sent successfully.');
return true;
}
catch (error) {
this.logger.error(`❌ Failed to send Telegram message: ${error.message}`);
if (error.response) {
this.logger.error(`Status: ${error.response.status}, Data: ${JSON.stringify(error.response.data)}`);
}
return false;
}
}
async sendIntelligenceReport(stats) {
const message = `
🚀 <b>Intaleq Map Intelligence Report</b>
━━━━━━━━━━━━━━━━━━
📅 <b>Window:</b> Last ${stats.days} days
📥 <b>Tracks Imported:</b> ${stats.syncResult} points
🛣️ <b>Roads Updated:</b> ${stats.updatedSegments} segments
✨ <b>New Roads Found:</b> ${stats.discoveredRoads} candidates
🕒 <b>Time Profiles:</b> ${stats.timeProfiles || 0} buckets
📍 <b>Database Total:</b> ${stats.totalPoints} points
━━━━━━━━━━━━━━━━━━
✅ <i>Deep Sync and Analysis Complete.</i>
`;
return this.sendMessage(message);
}
};
exports.TelegramService = TelegramService;
exports.TelegramService = TelegramService = TelegramService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], TelegramService);
//# sourceMappingURL=telegram.service.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"telegram.service.js","sourceRoot":"","sources":["../../src/common/telegram.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,2CAA+C;AAC/C,kDAA0B;AAGnB,IAAM,eAAe,uBAArB,MAAM,eAAe;IAKN;IAJH,MAAM,GAAG,IAAI,eAAM,CAAC,iBAAe,CAAC,IAAI,CAAC,CAAC;IAC1C,QAAQ,CAAS;IACjB,MAAM,CAAS;IAEhC,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;QAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,oBAAoB,CAAC,IAAI,EAAE,CAAC;QAC3E,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,kBAAkB,CAAC,IAAI,EAAE,CAAC;IACzE,CAAC;IAMD,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC/E,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;YACzF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,+BAA+B,IAAI,CAAC,QAAQ,cAAc,CAAC;YACvE,MAAM,eAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBACpB,OAAO,EAAE,IAAI,CAAC,MAAM;gBACpB,IAAI,EAAE,IAAI;gBACV,UAAU,EAAE,MAAM;aACnB,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,QAAQ,CAAC,MAAM,WAAW,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACtG,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAKD,KAAK,CAAC,sBAAsB,CAAC,KAO5B;QACC,MAAM,OAAO,GAAG;;;yBAGK,KAAK,CAAC,IAAI;6BACN,KAAK,CAAC,UAAU;4BACjB,KAAK,CAAC,eAAe;4BACrB,KAAK,CAAC,eAAe;2BACtB,KAAK,CAAC,YAAY,IAAI,CAAC;4BACtB,KAAK,CAAC,WAAW;;;KAGxC,CAAC;QACF,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;CACF,CAAA;AAhEY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,mBAAU,GAAE;yDAMwB,sBAAa,oBAAb,sBAAa;GALrC,eAAe,CAgE3B"}
-19
View File
@@ -1,19 +0,0 @@
import { Repository } from 'typeorm';
import { AdminBoundary } from './entities/admin-boundary.entity';
export declare class AdminBoundariesService {
private adminBoundaryRepo;
private readonly logger;
constructor(adminBoundaryRepo: Repository<AdminBoundary>);
importGeoJSON(targetCountryCode: string, geoJson: any): Promise<{
success: boolean;
imported: number;
errors: number;
skipped: number;
}>;
importFromFile(countryCode: string, filePath: string): Promise<{
success: boolean;
imported: number;
errors: number;
skipped: number;
}>;
}
-138
View File
@@ -1,138 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var AdminBoundariesService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminBoundariesService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const admin_boundary_entity_1 = require("./entities/admin-boundary.entity");
let AdminBoundariesService = AdminBoundariesService_1 = class AdminBoundariesService {
adminBoundaryRepo;
logger = new common_1.Logger(AdminBoundariesService_1.name);
constructor(adminBoundaryRepo) {
this.adminBoundaryRepo = adminBoundaryRepo;
}
async importGeoJSON(targetCountryCode, geoJson) {
this.logger.log(`Importing admin boundaries for ${targetCountryCode}...`);
let imported = 0;
let errors = 0;
let skipped = 0;
if (!geoJson || !geoJson.features || !Array.isArray(geoJson.features)) {
this.logger.error('Invalid GeoJSON format');
return { success: false, imported: 0, errors: 1, skipped: 0 };
}
for (const feature of geoJson.features) {
try {
const props = feature.properties || {};
const countryCode = props.country || props.iso_3166_1_alpha2;
if (targetCountryCode && countryCode && countryCode.toUpperCase() !== targetCountryCode.toUpperCase()) {
skipped++;
continue;
}
const subtype = (props.subtype || '').toLowerCase();
let adminLevel;
switch (subtype) {
case 'country':
adminLevel = 2;
break;
case 'region':
adminLevel = 4;
break;
case 'county':
adminLevel = 6;
break;
case 'localadmin':
adminLevel = 8;
break;
case 'locality':
adminLevel = 8;
break;
case 'neighborhood':
adminLevel = 10;
break;
case 'macrohood':
adminLevel = 9;
break;
case 'microhood':
adminLevel = 11;
break;
default:
adminLevel = parseInt(props.admin_level || props.adminLevel, 10);
}
if (isNaN(adminLevel)) {
skipped++;
continue;
}
const nameAr = props['names']?.['primary'] || props['names']?.['common'] || props['name_ar'] || props['name'];
const nameEn = props['names']?.['en'] || props['name_en'];
const isArabic = (text) => /[\u0600-\u06FF]/.test(text);
if (['JO', 'SY', 'EG'].includes(targetCountryCode.toUpperCase()) && nameAr && !isArabic(nameAr)) {
const alternativeAr = Object.values(props['names'] || {}).find(v => typeof v === 'string' && isArabic(v));
if (!alternativeAr) {
skipped++;
continue;
}
}
let geom = feature.geometry;
if (geom.type === 'Polygon') {
geom = { type: 'MultiPolygon', coordinates: [geom.coordinates] };
}
else if (geom.type !== 'MultiPolygon') {
skipped++;
continue;
}
const boundary = this.adminBoundaryRepo.create({
country_code: targetCountryCode.toUpperCase(),
admin_level: adminLevel,
name_ar: nameAr,
name_en: nameEn,
geom: geom,
});
await this.adminBoundaryRepo.save(boundary);
imported++;
}
catch (err) {
this.logger.error(`Error importing feature: ${err.message}`);
errors++;
}
}
this.logger.log(`Import finished. Imported: ${imported}, Errors: ${errors}, Skipped: ${skipped}`);
return { success: true, imported, errors, skipped };
}
async importFromFile(countryCode, filePath) {
const fs = require('fs');
if (!fs.existsSync(filePath)) {
this.logger.error(`File not found: ${filePath}`);
return { success: false, imported: 0, errors: 1, skipped: 0 };
}
try {
const data = fs.readFileSync(filePath, 'utf8');
const geoJson = JSON.parse(data);
return this.importGeoJSON(countryCode, geoJson);
}
catch (err) {
this.logger.error(`Failed to read or parse GeoJSON file: ${err.message}`);
return { success: false, imported: 0, errors: 1, skipped: 0 };
}
}
};
exports.AdminBoundariesService = AdminBoundariesService;
exports.AdminBoundariesService = AdminBoundariesService = AdminBoundariesService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(admin_boundary_entity_1.AdminBoundary)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object])
], AdminBoundariesService);
//# sourceMappingURL=admin-boundaries.service.js.map
@@ -1 +0,0 @@
{"version":3,"file":"admin-boundaries.service.js","sourceRoot":"","sources":["../../src/geocoding/admin-boundaries.service.ts"],"names":[],"mappings":"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"}
@@ -1,42 +0,0 @@
import { Repository, DataSource } from 'typeorm';
import { NeighborhoodPoint } from './entities/neighborhood-point.entity';
import { NeighborhoodPolygon } from './entities/neighborhood-polygon.entity';
import { AdminBoundary } from './entities/admin-boundary.entity';
export declare class AdministrativeLinkingService {
private dataSource;
private readonly neighborhoodPointRepo;
private readonly neighborhoodPolygonRepo;
private readonly adminBoundaryRepo;
private readonly logger;
constructor(dataSource: DataSource, neighborhoodPointRepo: Repository<NeighborhoodPoint>, neighborhoodPolygonRepo: Repository<NeighborhoodPolygon>, adminBoundaryRepo: Repository<AdminBoundary>);
private readonly overpassMirrors;
private fetchWithRetry;
syncOsmNeighborhoodPoints(bbox?: string, country?: string): Promise<{
detected_country: string;
osm_fetched: any;
country_points: number;
total_points_in_db: number;
linked_to_district: number;
}>;
generateVoronoiNeighborhoods(country?: string): Promise<{
status: string;
country: string;
total_polygons: number;
districts_processed: any;
}>;
linkPlaces(country: 'jordan' | 'syria' | 'egypt'): Promise<{
status: string;
country: "syria" | "jordan" | "egypt";
total_places: number;
linked_neighborhood: number;
linked_district: number;
linked_governorate: number;
sample_masoum: any;
rows_affected: any;
} | {
status: string;
error: string;
polygons: number;
places: number;
}>;
}
@@ -1,283 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var AdministrativeLinkingService_1;
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdministrativeLinkingService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const neighborhood_point_entity_1 = require("./entities/neighborhood-point.entity");
const neighborhood_polygon_entity_1 = require("./entities/neighborhood-polygon.entity");
const admin_boundary_entity_1 = require("./entities/admin-boundary.entity");
const axios_1 = __importDefault(require("axios"));
let AdministrativeLinkingService = AdministrativeLinkingService_1 = class AdministrativeLinkingService {
dataSource;
neighborhoodPointRepo;
neighborhoodPolygonRepo;
adminBoundaryRepo;
logger = new common_1.Logger(AdministrativeLinkingService_1.name);
constructor(dataSource, neighborhoodPointRepo, neighborhoodPolygonRepo, adminBoundaryRepo) {
this.dataSource = dataSource;
this.neighborhoodPointRepo = neighborhoodPointRepo;
this.neighborhoodPolygonRepo = neighborhoodPolygonRepo;
this.adminBoundaryRepo = adminBoundaryRepo;
}
overpassMirrors = [
'https://overpass-api.de/api/interpreter',
'https://overpass.kumi.systems/api/interpreter',
'https://z.overpass-api.de/api/interpreter'
];
async fetchWithRetry(data, retries = 3) {
for (let i = 0; i <= retries; i++) {
const url = this.overpassMirrors[i % this.overpassMirrors.length];
try {
const response = await axios_1.default.post(url, data, {
timeout: 45000,
headers: {
'User-Agent': 'IntaleqMapBot/1.0',
'Content-Type': 'application/x-www-form-urlencoded'
}
});
return response.data;
}
catch (error) {
this.logger.warn(`Mirror ${url} failed: ${error.message}. Trying next mirror...`);
if (i === retries)
throw error;
await new Promise(res => setTimeout(res, 2000));
}
}
}
async syncOsmNeighborhoodPoints(bbox = '31.86,35.94,32.22,36.25', country) {
this.logger.log(`Syncing neighborhood points for country: ${country || 'auto'}, bbox: ${bbox}`);
const query = `[out:json][timeout:900];node["place"~"neighbourhood|suburb|town"](${bbox});out;`;
try {
const data = await this.fetchWithRetry(`data=${encodeURIComponent(query)}`);
const elements = data.elements;
this.logger.log(`Found ${elements.length} points in OSM.`);
let detectedCountry = country;
if (!detectedCountry) {
const firstLat = parseFloat(bbox.split(',')[0]);
if (firstLat > 32.3)
detectedCountry = 'syria';
else if (firstLat < 31.0)
detectedCountry = 'egypt';
else
detectedCountry = 'jordan';
}
for (const e of elements) {
const nameAr = e.tags['name:ar'] || e.tags['name'];
const nameEn = e.tags['name:en'] || e.tags['name'];
const geometry = { type: 'Point', coordinates: [e.lon, e.lat] };
await this.dataSource.query(`
INSERT INTO neighborhood_points (osm_id, name_ar, name_en, place_type, geometry, country)
VALUES ($1, $2, $3, $4, ST_SetSRID(ST_GeomFromGeoJSON($5), 4326), $6)
ON CONFLICT (osm_id) DO UPDATE SET
name_ar = EXCLUDED.name_ar,
name_en = EXCLUDED.name_en,
place_type = EXCLUDED.place_type,
geometry = EXCLUDED.geometry,
country = COALESCE(EXCLUDED.country, neighborhood_points.country);
`, [e.id, nameAr, nameEn, e.tags['place'], JSON.stringify(geometry), detectedCountry]);
}
await this.dataSource.query(`
UPDATE neighborhood_points np
SET district_id = COALESCE(
(
SELECT id FROM admin_boundaries ab
WHERE ab.admin_level IN (6, 8)
AND ST_Contains(ab.geom::geometry, np.geometry::geometry)
ORDER BY ab.admin_level DESC
LIMIT 1
),
(
SELECT id FROM admin_boundaries ab
WHERE ab.admin_level IN (6, 8)
ORDER BY ab.geom::geometry <-> np.geometry::geometry
LIMIT 1
)
)
WHERE district_id IS NULL AND (country = $1 OR $1 IS NULL);
`, [detectedCountry]);
const totalPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points`);
const linkedPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE district_id IS NOT NULL`);
const countryPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE country = $1`, [detectedCountry]);
const diagnostics = {
detected_country: detectedCountry,
osm_fetched: elements.length,
country_points: parseInt(countryPoints[0].cnt),
total_points_in_db: parseInt(totalPoints[0].cnt),
linked_to_district: parseInt(linkedPoints[0].cnt),
};
this.logger.log(`Step 1 diagnostics: ${JSON.stringify(diagnostics)}`);
return diagnostics;
}
catch (error) {
this.logger.error(`Failed to sync OSM points: ${error.message}`, error.stack);
throw error;
}
}
async generateVoronoiNeighborhoods(country) {
this.logger.log(`Generating Voronoi polygons for ${country || 'all'} neighborhoods...`);
const whereClause = country ? `WHERE district_id IS NOT NULL AND country = '${country}'` : `WHERE district_id IS NOT NULL`;
const prePoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points ${whereClause}`);
const preDistricts = await this.dataSource.query(`SELECT district_id, count(*) as cnt FROM neighborhood_points ${whereClause} GROUP BY district_id`);
const deleteWhere = country ? `WHERE method = 'voronoi' AND country = '${country}'` : `WHERE method = 'voronoi'`;
await this.dataSource.query(`DELETE FROM neighborhood_polygons ${deleteWhere}`);
this.logger.log(`Deleted old voronoi polygons for ${country || 'all'}.`);
let totalInserted = 0;
for (const row of preDistricts) {
const districtId = row.district_id;
const pointCount = parseInt(row.cnt);
try {
if (pointCount >= 2) {
const result = await this.dataSource.query(`
INSERT INTO neighborhood_polygons (osm_id, name_ar, name_en, place_type, parent_id, method, country, geometry)
WITH voronoi_cells AS (
SELECT (ST_Dump(ST_VoronoiPolygons(
ST_Collect(np.geometry::geometry),
0.00001,
d.geom::geometry
))).geom as cell
FROM neighborhood_points np
JOIN admin_boundaries d ON d.id = np.district_id
WHERE np.district_id = $1
GROUP BY d.geom
),
matched AS (
SELECT
np.osm_id, np.name_ar, np.name_en, np.place_type, np.district_id, np.country,
ST_Multi(ST_Intersection(vc.cell::geometry, d.geom::geometry)) as geometry
FROM voronoi_cells vc
CROSS JOIN LATERAL (
SELECT np2.*
FROM neighborhood_points np2
WHERE np2.district_id = $1
ORDER BY np2.geometry::geometry <-> vc.cell::geometry
LIMIT 1
) np
JOIN admin_boundaries d ON d.id = $1
)
SELECT osm_id, name_ar, name_en, place_type, district_id, 'voronoi', country, geometry
FROM matched
WHERE ST_IsValid(geometry) AND NOT ST_IsEmpty(geometry)
`, [districtId]);
totalInserted += (result?.length || result?.[1] || 0);
}
else if (pointCount === 1) {
await this.dataSource.query(`
INSERT INTO neighborhood_polygons (osm_id, name_ar, name_en, place_type, parent_id, method, country, geometry)
SELECT np.osm_id, np.name_ar, np.name_en, np.place_type, np.district_id, 'voronoi', np.country, ST_Multi(d.geom::geometry)
FROM neighborhood_points np
JOIN admin_boundaries d ON d.id = np.district_id
WHERE np.district_id = $1
`, [districtId]);
totalInserted++;
}
}
catch (err) {
this.logger.error(`Voronoi FAILED for district ${districtId}: ${err.message}`);
}
}
const postCount = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_polygons ${country ? `WHERE country = '${country}'` : ''}`);
return {
status: 'success',
country: country || 'all',
total_polygons: parseInt(postCount[0].cnt),
districts_processed: preDistricts.length
};
}
async linkPlaces(country) {
const tableName = `places_${country}`;
const polyCount = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_polygons`);
const placeCount = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE location IS NOT NULL`);
this.logger.log(`linkPlaces pre-flight: ${polyCount[0].cnt} polygons, ${placeCount[0].cnt} places with location in ${tableName}`);
if (parseInt(polyCount[0].cnt) === 0) {
this.logger.error('ABORT: neighborhood_polygons is EMPTY. Run generate-voronoi first!');
return {
status: 'FAILED',
error: 'neighborhood_polygons table is empty. Run generate-voronoi first.',
polygons: 0,
places: parseInt(placeCount[0].cnt)
};
}
this.logger.log(`Linking administrative hierarchy for ${tableName} using KNN...`);
const updateResult = await this.dataSource.query(`
UPDATE ${tableName} p
SET
neighborhood_id = (
SELECT np.id FROM neighborhood_polygons np
WHERE (np.country = $1 OR np.country IS NULL)
ORDER BY p.location::geometry <-> np.geometry::geometry
LIMIT 1
),
sub_district_id = (
SELECT ab.id FROM admin_boundaries ab
WHERE ab.admin_level = 8
AND ST_Within(p.location::geometry, ab.geom::geometry)
LIMIT 1
),
district_id = (
SELECT ab.id FROM admin_boundaries ab
WHERE ab.admin_level = 6
AND ST_Within(p.location::geometry, ab.geom::geometry)
LIMIT 1
),
governorate_id = (
SELECT ab.id FROM admin_boundaries ab
WHERE ab.admin_level = 4
AND ST_Within(p.location::geometry, ab.geom::geometry)
LIMIT 1
)
WHERE p.location IS NOT NULL;
`, [country]);
const linkedNeighborhood = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE neighborhood_id IS NOT NULL`);
const linkedDistrict = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE district_id IS NOT NULL`);
const linkedGov = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE governorate_id IS NOT NULL`);
const totalPlaces = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName}`);
const samplePlace = await this.dataSource.query(`
SELECT p.id, p.name_ar, p.neighborhood_id, p.district_id, p.governorate_id,
n.name_ar as neighborhood_name
FROM ${tableName} p
LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id
WHERE p.name_ar LIKE '%معصوم%'
LIMIT 3
`);
const diagnostics = {
status: 'completed',
country,
total_places: parseInt(totalPlaces[0].cnt),
linked_neighborhood: parseInt(linkedNeighborhood[0].cnt),
linked_district: parseInt(linkedDistrict[0].cnt),
linked_governorate: parseInt(linkedGov[0].cnt),
sample_masoum: samplePlace,
rows_affected: updateResult?.[1] || 'unknown'
};
this.logger.log(`Step 3 diagnostics: ${JSON.stringify(diagnostics)}`);
return diagnostics;
}
};
exports.AdministrativeLinkingService = AdministrativeLinkingService;
exports.AdministrativeLinkingService = AdministrativeLinkingService = AdministrativeLinkingService_1 = __decorate([
(0, common_1.Injectable)(),
__param(1, (0, typeorm_1.InjectRepository)(neighborhood_point_entity_1.NeighborhoodPoint)),
__param(2, (0, typeorm_1.InjectRepository)(neighborhood_polygon_entity_1.NeighborhoodPolygon)),
__param(3, (0, typeorm_1.InjectRepository)(admin_boundary_entity_1.AdminBoundary)),
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.DataSource !== "undefined" && typeorm_2.DataSource) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, typeof (_c = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _c : Object, typeof (_d = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _d : Object])
], AdministrativeLinkingService);
//# sourceMappingURL=administrative-linking.service.js.map
File diff suppressed because one or more lines are too long
-4
View File
@@ -1,4 +0,0 @@
export declare class ReverseGeocodeDto {
lat: number;
lng: number;
}
-39
View File
@@ -1,39 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReverseGeocodeDto = void 0;
const class_validator_1 = require("class-validator");
const class_transformer_1 = require("class-transformer");
const swagger_1 = require("@nestjs/swagger");
class ReverseGeocodeDto {
lat;
lng;
}
exports.ReverseGeocodeDto = ReverseGeocodeDto;
__decorate([
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(-90),
(0, class_validator_1.Max)(90),
(0, class_transformer_1.Type)(() => Number),
(0, swagger_1.ApiProperty)({ description: 'Latitude' }),
__metadata("design:type", Number)
], ReverseGeocodeDto.prototype, "lat", void 0);
__decorate([
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(-180),
(0, class_validator_1.Max)(180),
(0, class_transformer_1.Type)(() => Number),
(0, swagger_1.ApiProperty)({ description: 'Longitude' }),
__metadata("design:type", Number)
], ReverseGeocodeDto.prototype, "lng", void 0);
//# sourceMappingURL=reverse-geocode.dto.js.map
@@ -1 +0,0 @@
{"version":3,"file":"reverse-geocode.dto.js","sourceRoot":"","sources":["../../../src/geocoding/dto/reverse-geocode.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAiE;AACjE,yDAAyC;AACzC,6CAA8C;AAE9C,MAAa,iBAAiB;IAO5B,GAAG,CAAS;IAQZ,GAAG,CAAS;CACb;AAhBD,8CAgBC;AATC;IANC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,EAAE,CAAC;IACR,IAAA,qBAAG,EAAC,EAAE,CAAC;IACP,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,qBAAW,EAAC,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;;8CAC7B;AAQZ;IANC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,GAAG,CAAC;IACT,IAAA,qBAAG,EAAC,GAAG,CAAC;IACR,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,qBAAW,EAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;;8CAC9B"}
-7
View File
@@ -1,7 +0,0 @@
export declare class SearchQueryDto {
q: string;
lat?: number;
lng?: number;
radius?: number;
country?: string;
}

Some files were not shown because too many files have changed in this diff Show More