django-cfg 1.5.8__py3-none-any.whl → 1.5.20__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +126 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Management command to load pre-built agent templates.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from django.contrib.auth.models import User
|
|
8
|
+
from django.core.management.base import CommandError
|
|
9
|
+
|
|
10
|
+
from django_cfg.management.utils import AdminCommand
|
|
11
|
+
|
|
12
|
+
from django_cfg.apps.business.agents.models.registry import AgentDefinition
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Command(AdminCommand):
|
|
16
|
+
"""Load agent definitions from templates."""
|
|
17
|
+
|
|
18
|
+
command_name = 'load_agent_templates'
|
|
19
|
+
help = 'Load pre-built agent templates'
|
|
20
|
+
|
|
21
|
+
def add_arguments(self, parser):
|
|
22
|
+
"""Add command arguments."""
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
'--list',
|
|
25
|
+
action='store_true',
|
|
26
|
+
help='List available templates'
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
'--load',
|
|
30
|
+
type=str,
|
|
31
|
+
nargs='*',
|
|
32
|
+
help='Load specific templates (space-separated names)'
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
'--load-all',
|
|
36
|
+
action='store_true',
|
|
37
|
+
help='Load all available templates'
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
'--creator',
|
|
41
|
+
type=str,
|
|
42
|
+
help='Username of agent creator (defaults to first superuser)'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def handle(self, *args, **options):
|
|
46
|
+
"""Handle command execution."""
|
|
47
|
+
if options['list']:
|
|
48
|
+
self._list_templates()
|
|
49
|
+
elif options['load'] or options['load_all']:
|
|
50
|
+
asyncio.run(self._load_templates(options))
|
|
51
|
+
else:
|
|
52
|
+
self.stdout.write(
|
|
53
|
+
self.style.ERROR('Please specify --list, --load, or --load-all')
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def _list_templates(self):
|
|
57
|
+
"""List available templates."""
|
|
58
|
+
templates = self._get_available_templates()
|
|
59
|
+
|
|
60
|
+
self.stdout.write(self.style.SUCCESS('📋 Available Agent Templates:'))
|
|
61
|
+
self.stdout.write('=' * 40)
|
|
62
|
+
|
|
63
|
+
for category, agents in templates.items():
|
|
64
|
+
self.stdout.write(f"\n{category.upper()}:")
|
|
65
|
+
for agent_name, agent_info in agents.items():
|
|
66
|
+
self.stdout.write(f" • {agent_name}: {agent_info['description']}")
|
|
67
|
+
|
|
68
|
+
async def _load_templates(self, options):
|
|
69
|
+
"""Load templates."""
|
|
70
|
+
creator = await self._get_creator_user(options.get('creator'))
|
|
71
|
+
templates = self._get_available_templates()
|
|
72
|
+
|
|
73
|
+
if options['load_all']:
|
|
74
|
+
# Load all templates
|
|
75
|
+
to_load = []
|
|
76
|
+
for category_templates in templates.values():
|
|
77
|
+
to_load.extend(category_templates.keys())
|
|
78
|
+
else:
|
|
79
|
+
to_load = options['load']
|
|
80
|
+
|
|
81
|
+
loaded_count = 0
|
|
82
|
+
|
|
83
|
+
for template_name in to_load:
|
|
84
|
+
# Find template
|
|
85
|
+
template_info = None
|
|
86
|
+
for category_templates in templates.values():
|
|
87
|
+
if template_name in category_templates:
|
|
88
|
+
template_info = category_templates[template_name]
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
if not template_info:
|
|
92
|
+
self.stdout.write(
|
|
93
|
+
self.style.WARNING(f"Template '{template_name}' not found")
|
|
94
|
+
)
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Check if agent already exists
|
|
98
|
+
if await AgentDefinition.objects.filter(name=template_name).aexists():
|
|
99
|
+
self.stdout.write(
|
|
100
|
+
self.style.WARNING(f"Agent '{template_name}' already exists, skipping")
|
|
101
|
+
)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Create agent
|
|
105
|
+
try:
|
|
106
|
+
agent_data = template_info.copy()
|
|
107
|
+
agent_data['name'] = template_name
|
|
108
|
+
agent_data['created_by'] = creator
|
|
109
|
+
|
|
110
|
+
await AgentDefinition.objects.acreate(**agent_data)
|
|
111
|
+
|
|
112
|
+
self.stdout.write(
|
|
113
|
+
self.style.SUCCESS(f"✅ Loaded template: {template_name}")
|
|
114
|
+
)
|
|
115
|
+
loaded_count += 1
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
self.stdout.write(
|
|
119
|
+
self.style.ERROR(f"Failed to load template '{template_name}': {e}")
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
self.stdout.write(
|
|
123
|
+
self.style.SUCCESS(f"\n🎉 Loaded {loaded_count} agent templates")
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def _get_available_templates(self):
|
|
127
|
+
"""Get available agent templates."""
|
|
128
|
+
return {
|
|
129
|
+
'content': {
|
|
130
|
+
'content_analyzer': {
|
|
131
|
+
'description': 'Analyze content sentiment, topics, and quality',
|
|
132
|
+
'instructions': 'Analyze content for sentiment, topics, keywords, and quality metrics.',
|
|
133
|
+
'deps_type': 'ContentDeps',
|
|
134
|
+
'output_type': 'AnalysisResult',
|
|
135
|
+
'category': 'content',
|
|
136
|
+
'model': 'openai:gpt-4o-mini',
|
|
137
|
+
},
|
|
138
|
+
'content_generator': {
|
|
139
|
+
'description': 'Generate high-quality content based on requirements',
|
|
140
|
+
'instructions': 'Generate engaging, well-structured content based on type, audience, and style requirements.',
|
|
141
|
+
'deps_type': 'ContentDeps',
|
|
142
|
+
'output_type': 'ProcessResult',
|
|
143
|
+
'category': 'content',
|
|
144
|
+
'model': 'openai:gpt-4o-mini',
|
|
145
|
+
},
|
|
146
|
+
'content_validator': {
|
|
147
|
+
'description': 'Validate content quality and compliance',
|
|
148
|
+
'instructions': 'Validate content for grammar, style, accuracy, and guideline compliance.',
|
|
149
|
+
'deps_type': 'ContentDeps',
|
|
150
|
+
'output_type': 'ValidationResult',
|
|
151
|
+
'category': 'content',
|
|
152
|
+
'model': 'openai:gpt-4o-mini',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
'data': {
|
|
156
|
+
'data_processor': {
|
|
157
|
+
'description': 'Process and transform data',
|
|
158
|
+
'instructions': 'Process, clean, and transform data according to specifications.',
|
|
159
|
+
'deps_type': 'DataProcessingDeps',
|
|
160
|
+
'output_type': 'ProcessResult',
|
|
161
|
+
'category': 'data',
|
|
162
|
+
'model': 'openai:gpt-4o-mini',
|
|
163
|
+
},
|
|
164
|
+
'data_validator': {
|
|
165
|
+
'description': 'Validate data quality and integrity',
|
|
166
|
+
'instructions': 'Validate data quality, check for errors, and ensure integrity.',
|
|
167
|
+
'deps_type': 'DataProcessingDeps',
|
|
168
|
+
'output_type': 'ValidationResult',
|
|
169
|
+
'category': 'data',
|
|
170
|
+
'model': 'openai:gpt-4o-mini',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
'business': {
|
|
174
|
+
'business_rules': {
|
|
175
|
+
'description': 'Apply business rules and logic',
|
|
176
|
+
'instructions': 'Apply business rules, validate decisions, and ensure compliance.',
|
|
177
|
+
'deps_type': 'BusinessLogicDeps',
|
|
178
|
+
'output_type': 'ProcessResult',
|
|
179
|
+
'category': 'business',
|
|
180
|
+
'model': 'openai:gpt-4o-mini',
|
|
181
|
+
},
|
|
182
|
+
'decision_maker': {
|
|
183
|
+
'description': 'Make decisions based on criteria',
|
|
184
|
+
'instructions': 'Analyze options and make informed decisions based on criteria and context.',
|
|
185
|
+
'deps_type': 'BusinessLogicDeps',
|
|
186
|
+
'output_type': 'ProcessResult',
|
|
187
|
+
'category': 'business',
|
|
188
|
+
'model': 'openai:gpt-4o-mini',
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async def _get_creator_user(self, username):
|
|
194
|
+
"""Get creator user."""
|
|
195
|
+
if username:
|
|
196
|
+
try:
|
|
197
|
+
return await User.objects.aget(username=username)
|
|
198
|
+
except User.DoesNotExist:
|
|
199
|
+
raise CommandError(f"User '{username}' not found")
|
|
200
|
+
else:
|
|
201
|
+
# Use first superuser
|
|
202
|
+
try:
|
|
203
|
+
return await User.objects.filter(is_superuser=True).afirst()
|
|
204
|
+
except User.DoesNotExist:
|
|
205
|
+
raise CommandError("No superuser found. Please create a superuser first or specify --creator")
|
|
@@ -5,17 +5,19 @@ Management command to show orchestrator status.
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from datetime import timedelta
|
|
7
7
|
|
|
8
|
-
from django.core.management.base import BaseCommand
|
|
9
8
|
from django.utils import timezone
|
|
10
9
|
|
|
10
|
+
from django_cfg.management.utils import SafeCommand
|
|
11
|
+
|
|
11
12
|
from django_cfg.apps.business.agents.integration.registry import get_registry
|
|
12
13
|
from django_cfg.apps.business.agents.models.execution import AgentExecution, WorkflowExecution
|
|
13
14
|
from django_cfg.apps.business.agents.models.registry import AgentDefinition
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
class Command(
|
|
17
|
+
class Command(SafeCommand):
|
|
17
18
|
"""Show Django Orchestrator status and statistics."""
|
|
18
19
|
|
|
20
|
+
command_name = 'orchestrator_status'
|
|
19
21
|
help = 'Display Django Orchestrator status and statistics'
|
|
20
22
|
|
|
21
23
|
def add_arguments(self, parser):
|
|
@@ -3,17 +3,19 @@ Knowledge Base statistics command.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from django.contrib.auth import get_user_model
|
|
6
|
-
from django.core.management.base import BaseCommand
|
|
7
6
|
from django.db import models
|
|
8
7
|
from django.db.models import Avg, Count, ExpressionWrapper, F, Q, Sum
|
|
9
8
|
from django.db.models.functions import Extract
|
|
10
9
|
|
|
10
|
+
from django_cfg.management.utils import SafeCommand
|
|
11
|
+
|
|
11
12
|
User = get_user_model()
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class Command(
|
|
15
|
+
class Command(SafeCommand):
|
|
15
16
|
"""Display Knowledge Base statistics."""
|
|
16
17
|
|
|
18
|
+
command_name = 'knowbase_stats'
|
|
17
19
|
help = 'Display Knowledge Base usage statistics'
|
|
18
20
|
|
|
19
21
|
def add_arguments(self, parser):
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
Setup command for Knowledge Base application.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from django.core.management.base import BaseCommand
|
|
6
5
|
from django.db import connection
|
|
7
6
|
|
|
7
|
+
from django_cfg.management.utils import AdminCommand
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
class Command(AdminCommand):
|
|
10
11
|
"""Setup Knowledge Base with pgvector extension and initial data."""
|
|
11
12
|
|
|
13
|
+
command_name = 'setup_knowbase'
|
|
12
14
|
help = 'Setup Knowledge Base with pgvector extension and run migrations'
|
|
13
15
|
|
|
14
16
|
def add_arguments(self, parser):
|
|
@@ -3,7 +3,9 @@ Management command to test newsletter sending functionality.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from django.contrib.auth import get_user_model
|
|
6
|
-
from django.core.management.base import
|
|
6
|
+
from django.core.management.base import CommandError
|
|
7
|
+
|
|
8
|
+
from django_cfg.management.utils import SafeCommand
|
|
7
9
|
|
|
8
10
|
from django_cfg.apps.business.newsletter.models import Newsletter, NewsletterCampaign, NewsletterSubscription
|
|
9
11
|
from django_cfg.apps.business.newsletter.services.email_service import NewsletterEmailService
|
|
@@ -11,7 +13,8 @@ from django_cfg.apps.business.newsletter.services.email_service import Newslette
|
|
|
11
13
|
User = get_user_model()
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class Command(
|
|
16
|
+
class Command(SafeCommand):
|
|
17
|
+
command_name = 'test_newsletter'
|
|
15
18
|
help = 'Test newsletter sending functionality'
|
|
16
19
|
|
|
17
20
|
def add_arguments(self, parser):
|
|
@@ -7,11 +7,12 @@ Uses questionary for interactive selection or accepts payment ID as argument.
|
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
9
9
|
import questionary
|
|
10
|
-
from django.core.management.base import BaseCommand
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
from rich.panel import Panel
|
|
13
12
|
from rich.table import Table
|
|
14
13
|
|
|
14
|
+
from django_cfg.management.utils import InteractiveCommand
|
|
15
|
+
|
|
15
16
|
from django_cfg.apps.business.payments.models import Payment
|
|
16
17
|
from django_cfg.apps.business.payments.services import PaymentService, CheckStatusRequest
|
|
17
18
|
from django_cfg.apps.business.payments.api.views import get_nowpayments_provider
|
|
@@ -19,7 +20,8 @@ from django_cfg.apps.business.payments.api.views import get_nowpayments_provider
|
|
|
19
20
|
console = Console()
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
class Command(
|
|
23
|
+
class Command(InteractiveCommand):
|
|
24
|
+
command_name = 'check_payment_status'
|
|
23
25
|
help = 'Check payment status interactively or by payment ID'
|
|
24
26
|
|
|
25
27
|
def add_arguments(self, parser):
|
|
@@ -8,11 +8,12 @@ from decimal import Decimal, InvalidOperation
|
|
|
8
8
|
|
|
9
9
|
import questionary
|
|
10
10
|
from django.contrib.auth import get_user_model
|
|
11
|
-
from django.core.management.base import BaseCommand
|
|
12
11
|
from rich.console import Console
|
|
13
12
|
from rich.panel import Panel
|
|
14
13
|
from rich.table import Table
|
|
15
14
|
|
|
15
|
+
from django_cfg.management.utils import InteractiveCommand
|
|
16
|
+
|
|
16
17
|
from django_cfg.apps.business.payments.models import Currency, Payment
|
|
17
18
|
from django_cfg.apps.business.payments.services import PaymentService, CreatePaymentRequest
|
|
18
19
|
from django_cfg.apps.business.payments.api.views import get_nowpayments_provider
|
|
@@ -21,7 +22,8 @@ User = get_user_model()
|
|
|
21
22
|
console = Console()
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
class Command(
|
|
25
|
+
class Command(InteractiveCommand):
|
|
26
|
+
command_name = 'create_payment'
|
|
25
27
|
help = 'Create a payment interactively using questionary wizard'
|
|
26
28
|
|
|
27
29
|
def add_arguments(self, parser):
|
|
@@ -5,19 +5,21 @@ Fetches available currencies from NowPayments and updates local database.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import questionary
|
|
8
|
-
from django.core.management.base import BaseCommand
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
from rich.panel import Panel
|
|
11
10
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
11
|
from rich.table import Table
|
|
13
12
|
|
|
13
|
+
from django_cfg.management.utils import AdminCommand
|
|
14
|
+
|
|
14
15
|
from django_cfg.apps.business.payments.models import Currency
|
|
15
16
|
from django_cfg.apps.business.payments.api.views import get_nowpayments_provider
|
|
16
17
|
|
|
17
18
|
console = Console()
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class Command(
|
|
21
|
+
class Command(AdminCommand):
|
|
22
|
+
command_name = 'sync_currencies'
|
|
21
23
|
help = 'Sync currencies from NowPayments provider'
|
|
22
24
|
|
|
23
25
|
def add_arguments(self, parser):
|
|
@@ -8,13 +8,14 @@ from .models import Message, Ticket
|
|
|
8
8
|
User = get_user_model()
|
|
9
9
|
|
|
10
10
|
class SenderSerializer(serializers.ModelSerializer):
|
|
11
|
-
avatar = serializers.SerializerMethodField()
|
|
11
|
+
avatar = serializers.SerializerMethodField(allow_null=True)
|
|
12
12
|
initials = serializers.ReadOnlyField()
|
|
13
13
|
|
|
14
14
|
class Meta:
|
|
15
15
|
model = User
|
|
16
16
|
fields = ['id', 'display_username', 'email', 'avatar', 'initials', 'is_staff', 'is_superuser']
|
|
17
|
-
read_only_fields
|
|
17
|
+
# Don't include avatar in read_only_fields to make it optional in OpenAPI schema
|
|
18
|
+
read_only_fields = ['id', 'display_username', 'email', 'initials', 'is_staff', 'is_superuser']
|
|
18
19
|
|
|
19
20
|
def get_avatar(self, obj) -> Optional[str]:
|
|
20
21
|
if obj.avatar:
|
|
@@ -32,6 +32,7 @@ class CentrifugoConfig(AppConfig):
|
|
|
32
32
|
Initialize app when Django starts.
|
|
33
33
|
|
|
34
34
|
Validates that all required Centrifugo dependencies are installed.
|
|
35
|
+
Registers signal handlers for JWT token customization.
|
|
35
36
|
"""
|
|
36
37
|
from django_cfg.modules.django_logging import get_logger
|
|
37
38
|
|
|
@@ -40,7 +41,7 @@ class CentrifugoConfig(AppConfig):
|
|
|
40
41
|
# Check dependencies if needed (only when using Centrifugo features)
|
|
41
42
|
self._check_dependencies_if_needed()
|
|
42
43
|
|
|
43
|
-
logger.info("Centrifugo app initialized")
|
|
44
|
+
logger.info("Centrifugo app initialized (middleware will inject JWT tokens)")
|
|
44
45
|
|
|
45
46
|
def _check_dependencies_if_needed(self):
|
|
46
47
|
"""
|
|
@@ -10,6 +10,7 @@ import { Centrifuge } from 'centrifuge';
|
|
|
10
10
|
export class CentrifugoRPCClient {
|
|
11
11
|
private centrifuge: Centrifuge;
|
|
12
12
|
private subscription: any;
|
|
13
|
+
private channelSubscriptions: Map<string, any> = new Map();
|
|
13
14
|
private pendingRequests: Map<string, { resolve: Function; reject: Function }> = new Map();
|
|
14
15
|
private readonly replyChannel: string;
|
|
15
16
|
private readonly timeout: number;
|
|
@@ -27,12 +28,7 @@ export class CentrifugoRPCClient {
|
|
|
27
28
|
token,
|
|
28
29
|
});
|
|
29
30
|
|
|
30
|
-
this.centrifuge.on('connected', (ctx) => {
|
|
31
|
-
console.log('✅ Connected to Centrifugo');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
31
|
this.centrifuge.on('disconnected', (ctx) => {
|
|
35
|
-
console.warn('Disconnected:', ctx);
|
|
36
32
|
// Reject all pending requests
|
|
37
33
|
this.pendingRequests.forEach(({ reject }) => {
|
|
38
34
|
reject(new Error('Disconnected from Centrifugo'));
|
|
@@ -43,6 +39,27 @@ export class CentrifugoRPCClient {
|
|
|
43
39
|
|
|
44
40
|
async connect(): Promise<void> {
|
|
45
41
|
return new Promise((resolve, reject) => {
|
|
42
|
+
let resolved = false;
|
|
43
|
+
|
|
44
|
+
// Listen to Centrifuge connection events
|
|
45
|
+
const onConnected = () => {
|
|
46
|
+
if (!resolved) {
|
|
47
|
+
resolved = true;
|
|
48
|
+
resolve();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const onError = (ctx: any) => {
|
|
53
|
+
if (!resolved) {
|
|
54
|
+
resolved = true;
|
|
55
|
+
reject(new Error(ctx.message || 'Connection error'));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
this.centrifuge.on('connected', onConnected);
|
|
60
|
+
this.centrifuge.on('error', onError);
|
|
61
|
+
|
|
62
|
+
// Start connection
|
|
46
63
|
this.centrifuge.connect();
|
|
47
64
|
|
|
48
65
|
// Subscribe to reply channel
|
|
@@ -53,11 +70,17 @@ export class CentrifugoRPCClient {
|
|
|
53
70
|
});
|
|
54
71
|
|
|
55
72
|
this.subscription.on('subscribed', () => {
|
|
56
|
-
|
|
73
|
+
// Subscription successful (optional, we already resolved on 'connected')
|
|
57
74
|
});
|
|
58
75
|
|
|
59
76
|
this.subscription.on('error', (ctx: any) => {
|
|
60
|
-
|
|
77
|
+
// Error code 105 = "already subscribed" (server-side subscription from JWT)
|
|
78
|
+
// This is not an error - the channel is already active via server-side subscription
|
|
79
|
+
if (ctx.error?.code === 105) {
|
|
80
|
+
// This is fine, server-side subscription exists
|
|
81
|
+
} else {
|
|
82
|
+
console.error(`Subscription error for ${this.replyChannel}:`, ctx.error);
|
|
83
|
+
}
|
|
61
84
|
});
|
|
62
85
|
|
|
63
86
|
this.subscription.subscribe();
|
|
@@ -65,9 +88,14 @@ export class CentrifugoRPCClient {
|
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
async disconnect(): Promise<void> {
|
|
91
|
+
// Unsubscribe from all event channels
|
|
92
|
+
this.unsubscribeAll();
|
|
93
|
+
|
|
94
|
+
// Unsubscribe from RPC reply channel
|
|
68
95
|
if (this.subscription) {
|
|
69
96
|
this.subscription.unsubscribe();
|
|
70
97
|
}
|
|
98
|
+
|
|
71
99
|
this.centrifuge.disconnect();
|
|
72
100
|
}
|
|
73
101
|
|
|
@@ -103,21 +131,17 @@ export class CentrifugoRPCClient {
|
|
|
103
131
|
// Publish request
|
|
104
132
|
await this.centrifuge.publish('rpc.requests', message);
|
|
105
133
|
|
|
106
|
-
console.log(`📤 RPC call: ${method} (${correlationId})`);
|
|
107
|
-
|
|
108
134
|
return promise;
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
private handleResponse(data: any): void {
|
|
112
138
|
const correlationId = data.correlation_id;
|
|
113
139
|
if (!correlationId) {
|
|
114
|
-
console.warn('Received response without correlation_id');
|
|
115
140
|
return;
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
const pending = this.pendingRequests.get(correlationId);
|
|
119
144
|
if (!pending) {
|
|
120
|
-
console.warn(`Received response for unknown correlation_id: ${correlationId}`);
|
|
121
145
|
return;
|
|
122
146
|
}
|
|
123
147
|
|
|
@@ -126,7 +150,6 @@ export class CentrifugoRPCClient {
|
|
|
126
150
|
if (data.error) {
|
|
127
151
|
pending.reject(new Error(data.error.message || 'RPC error'));
|
|
128
152
|
} else {
|
|
129
|
-
console.log(`📥 RPC response: ${correlationId}`);
|
|
130
153
|
pending.resolve(data.result);
|
|
131
154
|
}
|
|
132
155
|
}
|
|
@@ -134,4 +157,120 @@ export class CentrifugoRPCClient {
|
|
|
134
157
|
private generateCorrelationId(): string {
|
|
135
158
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
136
159
|
}
|
|
160
|
+
|
|
161
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
162
|
+
// Channel Subscription API (for gRPC events)
|
|
163
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Subscribe to a Centrifugo channel for real-time events.
|
|
167
|
+
*
|
|
168
|
+
* @param channel - Channel name (e.g., 'bot#bot-123#heartbeat')
|
|
169
|
+
* @param callback - Callback for received messages
|
|
170
|
+
* @returns Unsubscribe function
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* const unsubscribe = client.subscribe('bot#bot-123#heartbeat', (data) => {
|
|
174
|
+
* console.log('Heartbeat:', data);
|
|
175
|
+
* });
|
|
176
|
+
*
|
|
177
|
+
* // Later: unsubscribe when done
|
|
178
|
+
* unsubscribe();
|
|
179
|
+
*/
|
|
180
|
+
subscribe(channel: string, callback: (data: any) => void): () => void {
|
|
181
|
+
// Check if already subscribed
|
|
182
|
+
if (this.channelSubscriptions.has(channel)) {
|
|
183
|
+
return () => {}; // Return no-op unsubscribe
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Create new subscription
|
|
187
|
+
const sub = this.centrifuge.newSubscription(channel);
|
|
188
|
+
|
|
189
|
+
// Handle publications
|
|
190
|
+
sub.on('publication', (ctx: any) => {
|
|
191
|
+
callback(ctx.data);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Handle subscription lifecycle
|
|
195
|
+
sub.on('subscribed', () => {
|
|
196
|
+
// Subscription successful
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
sub.on('error', (ctx: any) => {
|
|
200
|
+
console.error(`Subscription error for ${channel}:`, ctx.error);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Start subscription
|
|
204
|
+
sub.subscribe();
|
|
205
|
+
|
|
206
|
+
// Store subscription
|
|
207
|
+
this.channelSubscriptions.set(channel, sub);
|
|
208
|
+
|
|
209
|
+
// Return unsubscribe function
|
|
210
|
+
return () => this.unsubscribe(channel);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Unsubscribe from a channel.
|
|
215
|
+
*
|
|
216
|
+
* @param channel - Channel name
|
|
217
|
+
*/
|
|
218
|
+
unsubscribe(channel: string): void {
|
|
219
|
+
const sub = this.channelSubscriptions.get(channel);
|
|
220
|
+
if (!sub) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
sub.unsubscribe();
|
|
225
|
+
this.channelSubscriptions.delete(channel);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Unsubscribe from all channels.
|
|
230
|
+
*/
|
|
231
|
+
unsubscribeAll(): void {
|
|
232
|
+
if (this.channelSubscriptions.size === 0) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.channelSubscriptions.forEach((sub, channel) => {
|
|
237
|
+
sub.unsubscribe();
|
|
238
|
+
});
|
|
239
|
+
this.channelSubscriptions.clear();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get list of active client-side subscriptions.
|
|
244
|
+
*/
|
|
245
|
+
getActiveSubscriptions(): string[] {
|
|
246
|
+
return Array.from(this.channelSubscriptions.keys());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get list of server-side subscriptions (from JWT token).
|
|
251
|
+
*
|
|
252
|
+
* These are channels automatically subscribed by Centrifugo server
|
|
253
|
+
* based on the 'channels' claim in the JWT token.
|
|
254
|
+
*/
|
|
255
|
+
getServerSideSubscriptions(): string[] {
|
|
256
|
+
try {
|
|
257
|
+
// Access Centrifuge.js internal state for server-side subs
|
|
258
|
+
// @ts-ignore - accessing internal property
|
|
259
|
+
const serverSubs = this.centrifuge._serverSubs || {};
|
|
260
|
+
return Object.keys(serverSubs);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get all active subscriptions (both client-side and server-side).
|
|
268
|
+
*/
|
|
269
|
+
getAllSubscriptions(): string[] {
|
|
270
|
+
const clientSubs = this.getActiveSubscriptions();
|
|
271
|
+
const serverSubs = this.getServerSideSubscriptions();
|
|
272
|
+
|
|
273
|
+
// Combine and deduplicate
|
|
274
|
+
return Array.from(new Set([...clientSubs, ...serverSubs]));
|
|
275
|
+
}
|
|
137
276
|
}
|
|
@@ -10,21 +10,22 @@ import logging
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import List
|
|
12
12
|
|
|
13
|
-
from django.core.management.base import
|
|
13
|
+
from django.core.management.base import CommandError
|
|
14
14
|
from django.utils.termcolors import colorize
|
|
15
15
|
|
|
16
|
+
from django_cfg.management.utils import AdminCommand
|
|
17
|
+
|
|
16
18
|
from django_cfg.apps.integrations.centrifugo.codegen.discovery import discover_rpc_methods_from_router
|
|
17
19
|
from django_cfg.apps.integrations.centrifugo.codegen.generators.python_thin import PythonThinGenerator
|
|
18
20
|
from django_cfg.apps.integrations.centrifugo.codegen.generators.typescript_thin import TypeScriptThinGenerator
|
|
19
21
|
from django_cfg.apps.integrations.centrifugo.codegen.generators.go_thin import GoThinGenerator
|
|
20
22
|
from django_cfg.apps.integrations.centrifugo.router import get_global_router
|
|
21
23
|
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
24
|
|
|
25
|
-
class Command(
|
|
25
|
+
class Command(AdminCommand):
|
|
26
26
|
"""Generate type-safe client SDKs for Centrifugo WebSocket RPC."""
|
|
27
27
|
|
|
28
|
+
command_name = 'generate_centrifugo_clients'
|
|
28
29
|
help = "Generate type-safe client SDKs for Centrifugo WebSocket RPC from @websocket_rpc handlers"
|
|
29
30
|
|
|
30
31
|
def add_arguments(self, parser):
|
|
@@ -131,11 +132,10 @@ class Command(BaseCommand):
|
|
|
131
132
|
if not methods:
|
|
132
133
|
self.stdout.write(
|
|
133
134
|
colorize(
|
|
134
|
-
"No RPC methods found.
|
|
135
|
+
"⚠️ No RPC methods found. Will generate base RPC client without API methods.",
|
|
135
136
|
fg="yellow",
|
|
136
137
|
)
|
|
137
138
|
)
|
|
138
|
-
return
|
|
139
139
|
|
|
140
140
|
# Create output directory
|
|
141
141
|
output_dir.mkdir(parents=True, exist_ok=True)
|