django-cfg 1.2.31__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -10
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +526 -222
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +465 -70
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +303 -151
  39. django_cfg/apps/payments/management/commands/manage_providers.py +333 -160
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +342 -152
  43. django_cfg/apps/payments/middleware/usage_tracking.py +249 -240
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +13 -18
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +172 -148
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -285
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +346 -467
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -481
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +234 -174
  74. django_cfg/apps/payments/services/providers/nowpayments.py +478 -0
  75. django_cfg/apps/payments/services/providers/registry.py +367 -301
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +210 -129
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +128 -103
  85. django_cfg/apps/payments/signals/subscription_signals.py +194 -142
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +45 -48
  93. django_cfg/apps/payments/urls_admin.py +33 -42
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/config.py +1 -1
  110. django_cfg/core/config.py +40 -4
  111. django_cfg/core/generation.py +25 -4
  112. django_cfg/core/integration/README.md +363 -0
  113. django_cfg/core/integration/__init__.py +47 -0
  114. django_cfg/core/integration/commands_collector.py +239 -0
  115. django_cfg/core/integration/display/__init__.py +15 -0
  116. django_cfg/core/integration/display/base.py +157 -0
  117. django_cfg/core/integration/display/ngrok.py +164 -0
  118. django_cfg/core/integration/display/startup.py +815 -0
  119. django_cfg/core/integration/url_integration.py +123 -0
  120. django_cfg/core/integration/version_checker.py +160 -0
  121. django_cfg/management/commands/auto_generate.py +4 -0
  122. django_cfg/management/commands/check_settings.py +6 -0
  123. django_cfg/management/commands/clear_constance.py +5 -2
  124. django_cfg/management/commands/create_token.py +6 -0
  125. django_cfg/management/commands/list_urls.py +6 -0
  126. django_cfg/management/commands/migrate_all.py +6 -0
  127. django_cfg/management/commands/migrator.py +3 -0
  128. django_cfg/management/commands/rundramatiq.py +6 -0
  129. django_cfg/management/commands/runserver_ngrok.py +51 -29
  130. django_cfg/management/commands/script.py +6 -0
  131. django_cfg/management/commands/show_config.py +12 -2
  132. django_cfg/management/commands/show_urls.py +4 -0
  133. django_cfg/management/commands/superuser.py +6 -0
  134. django_cfg/management/commands/task_clear.py +4 -1
  135. django_cfg/management/commands/task_status.py +3 -1
  136. django_cfg/management/commands/test_email.py +3 -0
  137. django_cfg/management/commands/test_telegram.py +6 -0
  138. django_cfg/management/commands/test_twilio.py +6 -0
  139. django_cfg/management/commands/tree.py +6 -0
  140. django_cfg/management/commands/validate_config.py +155 -149
  141. django_cfg/models/constance.py +31 -11
  142. django_cfg/models/payments.py +175 -492
  143. django_cfg/modules/django_logger.py +160 -146
  144. django_cfg/modules/django_unfold/dashboard.py +64 -16
  145. django_cfg/registry/core.py +1 -0
  146. django_cfg/template_archive/django_sample.zip +0 -0
  147. django_cfg/utils/smart_defaults.py +222 -571
  148. django_cfg/utils/toolkit.py +51 -11
  149. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/METADATA +4 -1
  150. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/RECORD +153 -185
  151. django_cfg/apps/payments/__init__.py +0 -8
  152. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  153. django_cfg/apps/payments/config/module.py +0 -70
  154. django_cfg/apps/payments/config/providers.py +0 -105
  155. django_cfg/apps/payments/config/settings.py +0 -96
  156. django_cfg/apps/payments/config/utils.py +0 -52
  157. django_cfg/apps/payments/decorators.py +0 -291
  158. django_cfg/apps/payments/management/commands/README.md +0 -146
  159. django_cfg/apps/payments/management/commands/currency_stats.py +0 -304
  160. django_cfg/apps/payments/managers/__init__.py +0 -23
  161. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  162. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  163. django_cfg/apps/payments/managers/currency_manager.py +0 -306
  164. django_cfg/apps/payments/managers/payment_manager.py +0 -192
  165. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  166. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  167. django_cfg/apps/payments/migrations/0002_network_providercurrency_and_more.py +0 -241
  168. django_cfg/apps/payments/migrations/0003_add_usd_rate_cache.py +0 -30
  169. django_cfg/apps/payments/models/events.py +0 -73
  170. django_cfg/apps/payments/serializers/__init__.py +0 -57
  171. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  172. django_cfg/apps/payments/serializers/balance.py +0 -59
  173. django_cfg/apps/payments/serializers/currencies.py +0 -63
  174. django_cfg/apps/payments/serializers/payments.py +0 -62
  175. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  176. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  177. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  178. django_cfg/apps/payments/services/cache/base.py +0 -30
  179. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  180. django_cfg/apps/payments/services/internal_types.py +0 -461
  181. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  182. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  183. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -76
  184. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  185. django_cfg/apps/payments/services/providers/cryptapi/__init__.py +0 -4
  186. django_cfg/apps/payments/services/providers/cryptapi/config.py +0 -8
  187. django_cfg/apps/payments/services/providers/cryptapi/models.py +0 -192
  188. django_cfg/apps/payments/services/providers/cryptapi/provider.py +0 -439
  189. django_cfg/apps/payments/services/providers/cryptomus/__init__.py +0 -4
  190. django_cfg/apps/payments/services/providers/cryptomus/models.py +0 -176
  191. django_cfg/apps/payments/services/providers/cryptomus/provider.py +0 -429
  192. django_cfg/apps/payments/services/providers/cryptomus/provider_v2.py +0 -564
  193. django_cfg/apps/payments/services/providers/models/__init__.py +0 -34
  194. django_cfg/apps/payments/services/providers/models/currencies.py +0 -190
  195. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +0 -4
  196. django_cfg/apps/payments/services/providers/nowpayments/models.py +0 -196
  197. django_cfg/apps/payments/services/providers/nowpayments/provider.py +0 -380
  198. django_cfg/apps/payments/services/providers/stripe/__init__.py +0 -4
  199. django_cfg/apps/payments/services/providers/stripe/models.py +0 -184
  200. django_cfg/apps/payments/services/providers/stripe/provider.py +0 -109
  201. django_cfg/apps/payments/services/security/__init__.py +0 -34
  202. django_cfg/apps/payments/services/security/error_handler.py +0 -635
  203. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  204. django_cfg/apps/payments/services/security/webhook_validator.py +0 -474
  205. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  206. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  207. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  208. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  209. django_cfg/apps/payments/tasks/__init__.py +0 -12
  210. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  211. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +0 -50
  212. django_cfg/apps/payments/templates/payments/base.html +0 -182
  213. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  214. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  215. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -43
  216. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  217. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -34
  218. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -148
  219. django_cfg/apps/payments/templates/payments/dashboard.html +0 -258
  220. django_cfg/apps/payments/templates/payments/dashboard_simple_test.html +0 -35
  221. django_cfg/apps/payments/templates/payments/payment_create.html +0 -579
  222. django_cfg/apps/payments/templates/payments/payment_detail.html +0 -373
  223. django_cfg/apps/payments/templates/payments/payment_list.html +0 -354
  224. django_cfg/apps/payments/templates/payments/stats.html +0 -261
  225. django_cfg/apps/payments/templates/payments/test.html +0 -213
  226. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  227. django_cfg/apps/payments/utils/__init__.py +0 -43
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -239
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -63
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -122
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -451
  241. django_cfg/apps/payments/views/templates/base.py +0 -212
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -158
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -244
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -66
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/template_archive/.gitignore +0 -1
  252. django_cfg/template_archive/__init__.py +0 -0
  253. django_cfg/urls.py +0 -33
  254. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  255. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  256. {django_cfg-1.2.31.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,299 +1,308 @@
1
1
  """
2
- Usage Tracking Middleware.
3
- Tracks API usage for billing, analytics, and monitoring.
2
+ Usage Tracking Middleware for the Universal Payment System v2.0.
3
+
4
+ Lightweight middleware for tracking API usage and analytics.
4
5
  """
5
6
 
6
- from django_cfg.modules.django_logger import get_logger
7
- import json
8
- from typing import Optional, Dict, Any
7
+ import time
8
+ from typing import Optional
9
9
  from django.http import HttpRequest, HttpResponse
10
10
  from django.utils.deprecation import MiddlewareMixin
11
- from django.conf import settings
12
11
  from django.utils import timezone
13
12
  from django.core.cache import cache
14
- from ..models import APIKey, Subscription, Transaction
15
- from ..services import RateLimitCache
16
13
 
17
- logger = get_logger("usage_tracking")
14
+ from ..config.helpers import MiddlewareConfigHelper
15
+ from django_cfg.modules.django_logger import get_logger
16
+
17
+ logger = get_logger("usage_tracking_middleware")
18
18
 
19
19
 
20
20
  class UsageTrackingMiddleware(MiddlewareMixin):
21
21
  """
22
- Middleware for tracking API usage and creating billing records.
22
+ Usage Tracking Middleware for API analytics and monitoring.
23
23
 
24
24
  Features:
25
- - Request/response logging
26
- - Usage analytics
27
- - Billing event creation
28
- - Performance monitoring
29
- - Error tracking
25
+ - Request/response time tracking
26
+ - Endpoint usage statistics
27
+ - User activity monitoring
28
+ - Performance metrics
29
+ - Error rate tracking
30
30
  """
31
31
 
32
32
  def __init__(self, get_response=None):
33
33
  super().__init__(get_response)
34
34
 
35
- # Enable/disable usage tracking
36
- self.enabled = getattr(settings, 'PAYMENTS_USAGE_TRACKING_ENABLED', True)
37
-
38
- # Paths to track (empty means track all API paths)
39
- self.tracked_paths = getattr(settings, 'PAYMENTS_TRACKED_PATHS', [])
40
-
41
- # Paths to exclude from tracking
42
- self.excluded_paths = getattr(settings, 'PAYMENTS_EXCLUDED_PATHS', [
43
- '/admin/',
44
- '/cfg/',
45
- '/api/v1/api-key/validate/',
46
- ])
47
-
48
- # Track request bodies (be careful with sensitive data)
49
- self.track_request_body = getattr(settings, 'PAYMENTS_TRACK_REQUEST_BODY', False)
35
+ # Load configuration from django-cfg
36
+ try:
37
+ middleware_config = MiddlewareConfigHelper.get_middleware_config()
38
+
39
+ # Configuration from django-cfg
40
+ self.enabled = middleware_config['usage_tracking_enabled']
41
+ self.track_anonymous = middleware_config['track_anonymous_usage']
42
+ self.api_prefixes = middleware_config['api_prefixes']
43
+ self.cache_timeout = middleware_config['cache_timeouts']['default']
44
+
45
+ # Static exempt paths
46
+ self.exempt_paths = [
47
+ '/api/health/',
48
+ '/admin/',
49
+ '/static/',
50
+ '/media/',
51
+ ]
52
+
53
+ except Exception as e:
54
+ logger.warning(f"Failed to load usage tracking config, using defaults: {e}")
55
+ # Fallback defaults
56
+ self.enabled = True
57
+ self.track_anonymous = False
58
+ self.api_prefixes = ['/api/']
59
+ self.exempt_paths = ['/api/health/', '/admin/']
60
+ self.cache_timeout = 3600
50
61
 
51
- # Track response bodies (be careful with large responses)
52
- self.track_response_body = getattr(settings, 'PAYMENTS_TRACK_RESPONSE_BODY', False)
62
+ logger.info(f"Usage Tracking Middleware initialized", extra={
63
+ 'enabled': self.enabled,
64
+ 'track_anonymous': self.track_anonymous
65
+ })
53
66
 
54
- def process_request(self, request: HttpRequest) -> None:
55
- """Process incoming request for usage tracking."""
56
-
67
+ def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
68
+ """
69
+ Process incoming request - start timing and prepare tracking.
70
+ """
57
71
  if not self.enabled:
58
- return
59
-
60
- # Skip excluded paths
61
- if self._is_excluded_path(request):
62
- return
72
+ return None
63
73
 
64
- # Only track if we have API key
65
- if not hasattr(request, 'payment_api_key'):
66
- return
74
+ # Check if we should track this request
75
+ if not self._should_track_request(request):
76
+ return None
67
77
 
68
- # Record request start time
69
- request._usage_start_time = timezone.now()
78
+ # Start timing
79
+ request._usage_start_time = time.time()
70
80
 
71
- # Prepare usage data
72
- request._usage_data = {
73
- 'api_key_id': request.payment_api_key.id,
74
- 'user_id': request.payment_api_key.user.id,
81
+ # Store request info for tracking
82
+ request._usage_info = {
75
83
  'method': request.method,
76
84
  'path': request.path,
77
- 'query_params': dict(request.GET),
78
- 'user_agent': request.META.get('HTTP_USER_AGENT', ''),
85
+ 'user_agent': request.META.get('HTTP_USER_AGENT', 'unknown'),
79
86
  'ip_address': self._get_client_ip(request),
80
- 'start_time': request._usage_start_time,
87
+ 'authenticated': hasattr(request, 'api_key') or (hasattr(request, 'user') and request.user.is_authenticated)
81
88
  }
82
89
 
83
- # Add subscription info if available
84
- if hasattr(request, 'payment_subscription'):
85
- request._usage_data.update({
86
- 'subscription_id': request.payment_subscription.id,
87
- 'endpoint_group_id': request.payment_subscription.endpoint_group.id,
88
- 'tier': request.payment_subscription.tier,
89
- })
90
-
91
- # Track request body if enabled and safe
92
- if self.track_request_body and self._is_safe_to_track_body(request):
93
- try:
94
- request._usage_data['request_body'] = request.body.decode('utf-8')[:1000] # Limit size
95
- except:
96
- pass
90
+ return None
97
91
 
98
92
  def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
99
- """Process response for usage tracking."""
100
-
101
- if not self.enabled or not hasattr(request, '_usage_data'):
93
+ """
94
+ Process response - track usage and performance metrics.
95
+ """
96
+ if not self.enabled or not hasattr(request, '_usage_start_time'):
102
97
  return response
103
98
 
104
99
  try:
105
100
  # Calculate response time
106
- end_time = timezone.now()
107
- response_time_ms = int((end_time - request._usage_start_time).total_seconds() * 1000)
108
-
109
- # Update usage data
110
- usage_data = request._usage_data
111
- usage_data.update({
112
- 'end_time': end_time,
113
- 'response_time_ms': response_time_ms,
114
- 'status_code': response.status_code,
115
- 'response_size': len(response.content) if hasattr(response, 'content') else 0,
116
- })
101
+ response_time = (time.time() - request._usage_start_time) * 1000 # ms
117
102
 
118
- # Track response body if enabled and safe
119
- if (self.track_response_body and
120
- self._is_safe_to_track_response(response) and
121
- response.status_code < 400):
122
- try:
123
- content = response.content.decode('utf-8')[:1000] # Limit size
124
- usage_data['response_body'] = content
125
- except:
126
- pass
127
-
128
- # Track error details for failed requests
129
- if response.status_code >= 400:
130
- usage_data['is_error'] = True
131
- usage_data['error_category'] = self._categorize_error(response.status_code)
132
- else:
133
- usage_data['is_error'] = False
134
-
135
- # Store in Redis for real-time analytics
136
- self._store_usage_data(usage_data)
137
-
138
- # Create billing transaction if needed
139
- if hasattr(request, 'payment_subscription'):
140
- self._create_billing_transaction(request.payment_subscription, usage_data)
141
-
142
- # Log for debugging
143
- logger.info(
144
- f"API usage tracked - User: {usage_data['user_id']}, "
145
- f"Path: {usage_data['path']}, "
146
- f"Status: {usage_data['status_code']}, "
147
- f"Time: {response_time_ms}ms"
148
- )
103
+ # Get usage info
104
+ usage_info = getattr(request, '_usage_info', {})
105
+
106
+ # Track the request
107
+ self._track_request(request, response, response_time, usage_info)
108
+
109
+ # Add performance headers
110
+ response['X-Response-Time'] = f"{response_time:.2f}ms"
149
111
 
150
112
  except Exception as e:
151
- logger.error(f"Error in usage tracking: {e}")
113
+ logger.warning(f"Usage tracking failed: {e}")
152
114
 
153
115
  return response
154
116
 
155
- def _is_excluded_path(self, request: HttpRequest) -> bool:
156
- """Check if path should be excluded from tracking."""
157
- path = request.path
158
-
159
- # Check excluded paths
160
- for excluded in self.excluded_paths:
161
- if path.startswith(excluded):
162
- return True
163
-
164
- # If tracked_paths is specified, only track those
165
- if self.tracked_paths:
166
- return not any(path.startswith(tracked) for tracked in self.tracked_paths)
167
-
168
- return False
169
-
170
- def _get_client_ip(self, request: HttpRequest) -> Optional[str]:
171
- """Get client IP address."""
172
-
173
- # Check for forwarded headers first
174
- forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
175
- if forwarded_for:
176
- return forwarded_for.split(',')[0].strip()
177
-
178
- # Check for real IP header
179
- real_ip = request.META.get('HTTP_X_REAL_IP')
180
- if real_ip:
181
- return real_ip
182
-
183
- # Fallback to remote address
184
- return request.META.get('REMOTE_ADDR')
185
-
186
- def _is_safe_to_track_body(self, request: HttpRequest) -> bool:
187
- """Check if it's safe to track request body."""
117
+ def _should_track_request(self, request: HttpRequest) -> bool:
118
+ """
119
+ Determine if we should track this request.
120
+ """
121
+ # Check if path is in API prefixes
122
+ is_api_request = any(request.path.startswith(prefix) for prefix in self.api_prefixes)
188
123
 
189
- # Don't track large bodies
190
- content_length = request.META.get('CONTENT_LENGTH')
191
- if content_length and int(content_length) > 10000: # 10KB limit
124
+ if not is_api_request:
192
125
  return False
193
126
 
194
- # Don't track file uploads
195
- content_type = request.META.get('CONTENT_TYPE', '')
196
- if 'multipart/form-data' in content_type:
127
+ # Check exempt paths
128
+ if request.path in self.exempt_paths:
197
129
  return False
198
130
 
199
- # Don't track sensitive endpoints
200
- sensitive_paths = ['/api/v1/api-key/', '/api/v1/payment/']
201
- path = request.path
202
- if any(path.startswith(sensitive) for sensitive in sensitive_paths):
203
- return False
131
+ # Check if we should track anonymous requests
132
+ if not self.track_anonymous:
133
+ is_authenticated = (
134
+ hasattr(request, 'api_key') or
135
+ (hasattr(request, 'user') and request.user.is_authenticated)
136
+ )
137
+ if not is_authenticated:
138
+ return False
204
139
 
205
140
  return True
206
141
 
207
- def _is_safe_to_track_response(self, response: HttpResponse) -> bool:
208
- """Check if it's safe to track response body."""
209
-
210
- # Don't track large responses
211
- if hasattr(response, 'content') and len(response.content) > 10000: # 10KB limit
212
- return False
213
-
214
- # Only track JSON responses
215
- content_type = response.get('Content-Type', '')
216
- if 'application/json' not in content_type:
217
- return False
218
-
219
- return True
142
+ def _track_request(self, request: HttpRequest, response: HttpResponse, response_time: float, usage_info: dict):
143
+ """
144
+ Track request usage and performance metrics.
145
+ """
146
+ try:
147
+ # Get user identifier
148
+ user_id = None
149
+ if hasattr(request, 'api_key'):
150
+ user_id = request.api_key.user.id
151
+ elif hasattr(request, 'user') and request.user.is_authenticated:
152
+ user_id = request.user.id
153
+
154
+ # Track endpoint usage
155
+ self._track_endpoint_usage(usage_info['path'], usage_info['method'], response.status_code)
156
+
157
+ # Track user activity
158
+ if user_id:
159
+ self._track_user_activity(user_id, usage_info['path'], response_time)
160
+
161
+ # Track performance metrics
162
+ self._track_performance_metrics(usage_info['path'], response_time, response.status_code)
163
+
164
+ # Track errors
165
+ if response.status_code >= 400:
166
+ self._track_error(usage_info, response.status_code)
167
+
168
+ # Log request
169
+ logger.debug(f"Request tracked", extra={
170
+ 'path': usage_info['path'],
171
+ 'method': usage_info['method'],
172
+ 'status_code': response.status_code,
173
+ 'response_time_ms': round(response_time, 2),
174
+ 'user_id': user_id,
175
+ 'authenticated': usage_info['authenticated']
176
+ })
177
+
178
+ except Exception as e:
179
+ logger.warning(f"Failed to track request: {e}")
220
180
 
221
- def _categorize_error(self, status_code: int) -> str:
222
- """Categorize error by status code."""
223
-
224
- if 400 <= status_code < 500:
225
- return 'client_error'
226
- elif 500 <= status_code < 600:
227
- return 'server_error'
228
- else:
229
- return 'unknown_error'
181
+ def _track_endpoint_usage(self, path: str, method: str, status_code: int):
182
+ """
183
+ Track endpoint usage statistics.
184
+ """
185
+ try:
186
+ today = timezone.now().date().isoformat()
187
+ hour = timezone.now().hour
188
+
189
+ # Daily endpoint usage
190
+ daily_key = f"endpoint_usage:{path}:{method}:{today}"
191
+ cache.set(daily_key, cache.get(daily_key, 0) + 1, timeout=86400 * 2)
192
+
193
+ # Hourly endpoint usage
194
+ hourly_key = f"endpoint_usage_hourly:{path}:{method}:{today}:{hour}"
195
+ cache.set(hourly_key, cache.get(hourly_key, 0) + 1, timeout=86400)
196
+
197
+ # Status code tracking
198
+ status_key = f"endpoint_status:{path}:{method}:{status_code}:{today}"
199
+ cache.set(status_key, cache.get(status_key, 0) + 1, timeout=86400 * 2)
200
+
201
+ except Exception as e:
202
+ logger.warning(f"Failed to track endpoint usage: {e}")
230
203
 
231
- def _store_usage_data(self, usage_data: Dict[str, Any]):
232
- """Store usage data in Redis for analytics."""
233
-
204
+ def _track_user_activity(self, user_id: int, path: str, response_time: float):
205
+ """
206
+ Track user activity and performance.
207
+ """
234
208
  try:
235
- # Store daily usage stats
236
- date_key = usage_data['start_time'].strftime('%Y-%m-%d')
237
- user_id = usage_data['user_id']
238
-
239
- # Increment counters using Django cache
240
- daily_usage_key = f"payments:daily_usage:{user_id}:{date_key}"
241
- current_usage = cache.get(daily_usage_key, 0)
242
- cache.set(daily_usage_key, current_usage + 1, timeout=86400) # 24 hours
243
-
244
- if usage_data.get('subscription_id'):
245
- sub_usage_key = f"payments:subscription_usage:{usage_data['subscription_id']}:{date_key}"
246
- current_sub_usage = cache.get(sub_usage_key, 0)
247
- cache.set(sub_usage_key, current_sub_usage + 1, timeout=86400) # 24 hours
248
-
249
- # Store performance metrics
250
- if usage_data['response_time_ms'] > 0:
251
- perf_key = f"payments:performance:{usage_data['path'].replace('/', '_')}"
252
- response_times = cache.get(perf_key, [])
253
- response_times.append(usage_data['response_time_ms'])
254
- # Keep only last 100 measurements
255
- if len(response_times) > 100:
256
- response_times = response_times[-100:]
257
- cache.set(perf_key, response_times, timeout=3600) # 1 hour
258
-
259
- # Store error metrics
260
- if usage_data.get('is_error'):
261
- error_key = f"payments:errors:{usage_data['path'].replace('/', '_')}:{usage_data['status_code']}"
262
- current_errors = cache.get(error_key, 0)
263
- cache.set(error_key, current_errors + 1, timeout=3600) # 1 hour
209
+ today = timezone.now().date().isoformat()
210
+
211
+ # Update last activity
212
+ cache.set(f"user_last_activity:{user_id}", timezone.now().isoformat(), timeout=86400 * 30)
213
+
214
+ # Daily user requests
215
+ daily_requests_key = f"user_requests:{user_id}:{today}"
216
+ cache.set(daily_requests_key, cache.get(daily_requests_key, 0) + 1, timeout=86400 * 2)
217
+
218
+ # User endpoint usage
219
+ user_endpoint_key = f"user_endpoint:{user_id}:{path}:{today}"
220
+ cache.set(user_endpoint_key, cache.get(user_endpoint_key, 0) + 1, timeout=86400 * 2)
221
+
222
+ # User performance tracking
223
+ perf_key = f"user_performance:{user_id}:{today}"
224
+ perf_data = cache.get(perf_key, {'total_time': 0.0, 'request_count': 0})
225
+ perf_data['total_time'] += response_time
226
+ perf_data['request_count'] += 1
227
+ perf_data['avg_response_time'] = perf_data['total_time'] / perf_data['request_count']
228
+
229
+ cache.set(perf_key, perf_data, timeout=86400 * 2)
264
230
 
265
231
  except Exception as e:
266
- logger.error(f"Error storing usage data in Redis: {e}")
232
+ logger.warning(f"Failed to track user activity: {e}")
267
233
 
268
- def _create_billing_transaction(self, subscription: Subscription, usage_data: Dict[str, Any]):
269
- """Create billing transaction for usage-based pricing."""
270
-
234
+ def _track_performance_metrics(self, path: str, response_time: float, status_code: int):
235
+ """
236
+ Track performance metrics for monitoring.
237
+ """
271
238
  try:
272
- # Only create transaction for successful requests
273
- if usage_data.get('is_error'):
274
- return
275
-
276
- # Check if this endpoint has usage-based pricing
277
- # For now, we'll create a small transaction for each API call
278
- # This could be batched or calculated differently based on business logic
279
-
280
- cost_per_request = 0.001 # $0.001 per request (example)
281
-
282
- # Create transaction record
283
- Transaction.objects.create(
284
- user=subscription.user,
285
- subscription=subscription,
286
- transaction_type='debit',
287
- amount_usd=-cost_per_request, # Negative for debit
288
- description=f"API usage: {usage_data['method']} {usage_data['path']}",
289
- metadata={
290
- 'api_call_id': f"{usage_data['api_key_id']}_{usage_data['start_time'].timestamp()}",
291
- 'endpoint': usage_data['path'],
292
- 'method': usage_data['method'],
293
- 'response_time_ms': usage_data['response_time_ms'],
294
- 'status_code': usage_data['status_code'],
295
- }
296
- )
239
+ today = timezone.now().date().isoformat()
240
+
241
+ # Global performance metrics
242
+ global_perf_key = f"global_performance:{today}"
243
+ global_perf = cache.get(global_perf_key, {
244
+ 'total_requests': 0,
245
+ 'total_time': 0.0,
246
+ 'slow_requests': 0, # > 1000ms
247
+ 'fast_requests': 0, # < 100ms
248
+ })
249
+
250
+ global_perf['total_requests'] += 1
251
+ global_perf['total_time'] += response_time
252
+
253
+ if response_time > 1000:
254
+ global_perf['slow_requests'] += 1
255
+ elif response_time < 100:
256
+ global_perf['fast_requests'] += 1
257
+
258
+ global_perf['avg_response_time'] = global_perf['total_time'] / global_perf['total_requests']
259
+
260
+ cache.set(global_perf_key, global_perf, timeout=86400 * 2)
261
+
262
+ # Update system average response time for adaptive rate limiting
263
+ cache.set('system_avg_response_time', global_perf['avg_response_time'], timeout=300)
297
264
 
298
265
  except Exception as e:
299
- logger.error(f"Error creating billing transaction: {e}")
266
+ logger.warning(f"Failed to track performance metrics: {e}")
267
+
268
+ def _track_error(self, usage_info: dict, status_code: int):
269
+ """
270
+ Track error occurrences for monitoring.
271
+ """
272
+ try:
273
+ today = timezone.now().date().isoformat()
274
+
275
+ # Global error tracking
276
+ error_key = f"errors:{status_code}:{today}"
277
+ cache.set(error_key, cache.get(error_key, 0) + 1, timeout=86400 * 7)
278
+
279
+ # Endpoint-specific error tracking
280
+ endpoint_error_key = f"endpoint_errors:{usage_info['path']}:{status_code}:{today}"
281
+ cache.set(endpoint_error_key, cache.get(endpoint_error_key, 0) + 1, timeout=86400 * 7)
282
+
283
+ # Log significant errors
284
+ if status_code >= 500:
285
+ logger.error(f"Server error tracked", extra={
286
+ 'status_code': status_code,
287
+ 'path': usage_info['path'],
288
+ 'method': usage_info['method'],
289
+ 'ip_address': usage_info['ip_address'],
290
+ 'user_agent': usage_info['user_agent']
291
+ })
292
+
293
+ except Exception as e:
294
+ logger.warning(f"Failed to track error: {e}")
295
+
296
+ def _get_client_ip(self, request: HttpRequest) -> str:
297
+ """
298
+ Get client IP address from request.
299
+ """
300
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
301
+ if x_forwarded_for:
302
+ return x_forwarded_for.split(',')[0].strip()
303
+
304
+ x_real_ip = request.META.get('HTTP_X_REAL_IP')
305
+ if x_real_ip:
306
+ return x_real_ip
307
+
308
+ return request.META.get('REMOTE_ADDR', 'unknown')