django-cfg 1.2.29__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 (258) 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 -9
  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 +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  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 +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  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 +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  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 +207 -67
  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 -284
  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 +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  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 +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  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 +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  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 +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  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/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  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 -62
  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 -111
  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 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  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 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  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 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,296 +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
- import logging
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
- from ..models import APIKey, Subscription, Transaction
14
- from ..services import RateLimitCache
12
+ from django.core.cache import cache
13
+
14
+ from ..config.helpers import MiddlewareConfigHelper
15
+ from django_cfg.modules.django_logger import get_logger
15
16
 
16
- logger = logging.getLogger(__name__)
17
+ logger = get_logger("usage_tracking_middleware")
17
18
 
18
19
 
19
20
  class UsageTrackingMiddleware(MiddlewareMixin):
20
21
  """
21
- Middleware for tracking API usage and creating billing records.
22
+ Usage Tracking Middleware for API analytics and monitoring.
22
23
 
23
24
  Features:
24
- - Request/response logging
25
- - Usage analytics
26
- - Billing event creation
27
- - Performance monitoring
28
- - Error tracking
25
+ - Request/response time tracking
26
+ - Endpoint usage statistics
27
+ - User activity monitoring
28
+ - Performance metrics
29
+ - Error rate tracking
29
30
  """
30
31
 
31
32
  def __init__(self, get_response=None):
32
33
  super().__init__(get_response)
33
- self.redis_service = RedisService()
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
34
 
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."""
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)
172
123
 
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."""
188
-
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
+ """
208
+ try:
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)
230
+
231
+ except Exception as e:
232
+ logger.warning(f"Failed to track user activity: {e}")
233
+
234
+ def _track_performance_metrics(self, path: str, response_time: float, status_code: int):
235
+ """
236
+ Track performance metrics for monitoring.
237
+ """
234
238
  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
240
- self.redis_service.increment_daily_usage(user_id, date_key)
241
-
242
- if usage_data.get('subscription_id'):
243
- self.redis_service.increment_subscription_usage(
244
- usage_data['subscription_id'],
245
- date_key
246
- )
247
-
248
- # Store performance metrics
249
- if usage_data['response_time_ms'] > 0:
250
- self.redis_service.record_response_time(
251
- usage_data['path'],
252
- usage_data['response_time_ms']
253
- )
254
-
255
- # Store error metrics
256
- if usage_data.get('is_error'):
257
- self.redis_service.increment_error_count(
258
- usage_data['path'],
259
- usage_data['status_code']
260
- )
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)
261
264
 
262
265
  except Exception as e:
263
- logger.error(f"Error storing usage data in Redis: {e}")
266
+ logger.warning(f"Failed to track performance metrics: {e}")
264
267
 
265
- def _create_billing_transaction(self, subscription: Subscription, usage_data: Dict[str, Any]):
266
- """Create billing transaction for usage-based pricing."""
267
-
268
+ def _track_error(self, usage_info: dict, status_code: int):
269
+ """
270
+ Track error occurrences for monitoring.
271
+ """
268
272
  try:
269
- # Only create transaction for successful requests
270
- if usage_data.get('is_error'):
271
- return
272
-
273
- # Check if this endpoint has usage-based pricing
274
- # For now, we'll create a small transaction for each API call
275
- # This could be batched or calculated differently based on business logic
276
-
277
- cost_per_request = 0.001 # $0.001 per request (example)
278
-
279
- # Create transaction record
280
- Transaction.objects.create(
281
- user=subscription.user,
282
- subscription=subscription,
283
- transaction_type='debit',
284
- amount_usd=-cost_per_request, # Negative for debit
285
- description=f"API usage: {usage_data['method']} {usage_data['path']}",
286
- metadata={
287
- 'api_call_id': f"{usage_data['api_key_id']}_{usage_data['start_time'].timestamp()}",
288
- 'endpoint': usage_data['path'],
289
- 'method': usage_data['method'],
290
- 'response_time_ms': usage_data['response_time_ms'],
291
- 'status_code': usage_data['status_code'],
292
- }
293
- )
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
+ })
294
292
 
295
293
  except Exception as e:
296
- logger.error(f"Error creating billing transaction: {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')