ntc-dev-kit 1.0.0__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 (148) hide show
  1. ntc/__init__.py +5 -0
  2. ntc/app_creator.py +296 -0
  3. ntc/cli.py +4 -0
  4. ntc/cli_enhanced.py +820 -0
  5. ntc/composer/__init__.py +0 -0
  6. ntc/composer/main.py +443 -0
  7. ntc/deployment/__init__.py +0 -0
  8. ntc/templates/__init__.py +2 -0
  9. ntc/templates/auth_app/__init__.py +0 -0
  10. ntc/templates/auth_app/admin.py +24 -0
  11. ntc/templates/auth_app/apps.py +6 -0
  12. ntc/templates/auth_app/models.py +30 -0
  13. ntc/templates/auth_app/serializers.py +101 -0
  14. ntc/templates/auth_app/urls.py +19 -0
  15. ntc/templates/auth_app/views.py +175 -0
  16. ntc/templates/drf_structure/__init__.py +1 -0
  17. ntc/templates/drf_structure/drf_structure.md +83 -0
  18. ntc/templates/drf_structure/main.py +389 -0
  19. ntc/templates/drf_structure/structure.json +126 -0
  20. ntc/templates/drf_structure/templates/.entry/start_core_backend.sh +37 -0
  21. ntc/templates/drf_structure/templates/.entry/start_tasks_backend.sh +30 -0
  22. ntc/templates/drf_structure/templates/CLAUDE.md +194 -0
  23. ntc/templates/drf_structure/templates/README.md +117 -0
  24. ntc/templates/drf_structure/templates/apps/app_admin.py +3 -0
  25. ntc/templates/drf_structure/templates/apps/app_apps.py +6 -0
  26. ntc/templates/drf_structure/templates/apps/app_models.py +4 -0
  27. ntc/templates/drf_structure/templates/apps/app_urls.py +8 -0
  28. ntc/templates/drf_structure/templates/apps/app_views.py +6 -0
  29. ntc/templates/drf_structure/templates/apps/common_apps.py +6 -0
  30. ntc/templates/drf_structure/templates/apps/common_models.py +8 -0
  31. ntc/templates/drf_structure/templates/config/gunicorn_config.py +9 -0
  32. ntc/templates/drf_structure/templates/config/http.conf +33 -0
  33. ntc/templates/drf_structure/templates/core/base_model.py +9 -0
  34. ntc/templates/drf_structure/templates/core/formatter.py +5 -0
  35. ntc/templates/drf_structure/templates/core/logger.py +8 -0
  36. ntc/templates/drf_structure/templates/core/pagination.py +6 -0
  37. ntc/templates/drf_structure/templates/core/response_wrapper.py +7 -0
  38. ntc/templates/drf_structure/templates/core/services/azure/email/backend.py +108 -0
  39. ntc/templates/drf_structure/templates/core/services/azure/email/client.py +296 -0
  40. ntc/templates/drf_structure/templates/core/services/azure/email/notifier.py +102 -0
  41. ntc/templates/drf_structure/templates/core/services/object_storage.py +285 -0
  42. ntc/templates/drf_structure/templates/django_project/asgi.py +5 -0
  43. ntc/templates/drf_structure/templates/django_project/celery.py +8 -0
  44. ntc/templates/drf_structure/templates/django_project/dev_settings.py +2 -0
  45. ntc/templates/drf_structure/templates/django_project/django_settings.txt +322 -0
  46. ntc/templates/drf_structure/templates/django_project/management_command.py +114 -0
  47. ntc/templates/drf_structure/templates/django_project/prod_settings.py +3 -0
  48. ntc/templates/drf_structure/templates/django_project/schema.py +7 -0
  49. ntc/templates/drf_structure/templates/django_project/urls.py +6 -0
  50. ntc/templates/drf_structure/templates/django_project/wsgi.py +5 -0
  51. ntc/templates/drf_structure/templates/docker/docker-compose.yml +11 -0
  52. ntc/templates/drf_structure/templates/knowledge/knowledge_description.md +201 -0
  53. ntc/templates/drf_structure/templates/knowledge/knowledge_design_pattern.md +722 -0
  54. ntc/templates/drf_structure/templates/knowledge/knowledge_structure.md +151 -0
  55. ntc/templates/drf_structure/templates/root/README.md +307 -0
  56. ntc/templates/drf_structure/templates/root/django_env.txt +98 -0
  57. ntc/templates/drf_structure/templates/root/django_requirements.txt +24 -0
  58. ntc/templates/drf_structure/templates/root/gitignore.txt +6 -0
  59. ntc/templates/drf_structure/templates/root/manage.py +11 -0
  60. ntc/templates/flutter/.gitignore +44 -0
  61. ntc/templates/flutter/.knowledge/knowledge_description.md +215 -0
  62. ntc/templates/flutter/.knowledge/knowledge_design_pattern.md +310 -0
  63. ntc/templates/flutter/.knowledge/knowledge_structure.md +83 -0
  64. ntc/templates/flutter/CLAUDE.md +190 -0
  65. ntc/templates/flutter/__init__.py +0 -0
  66. ntc/templates/flutter/analysis_options.yaml +9 -0
  67. ntc/templates/flutter/main.py +187 -0
  68. ntc/templates/flutter/pubspec.yaml +65 -0
  69. ntc/templates/flutter/structure.json +51 -0
  70. ntc/templates/nextjs/.env.example +10 -0
  71. ntc/templates/nextjs/.gitignore +36 -0
  72. ntc/templates/nextjs/CLAUDE.md +181 -0
  73. ntc/templates/nextjs/README.md +94 -0
  74. ntc/templates/nextjs/__init__.py +0 -0
  75. ntc/templates/nextjs/knowledge/knowledge_description.md +208 -0
  76. ntc/templates/nextjs/knowledge/knowledge_design_pattern.md +460 -0
  77. ntc/templates/nextjs/knowledge/knowledge_structure.md +75 -0
  78. ntc/templates/nextjs/main.py +224 -0
  79. ntc/templates/nextjs/package.json +25 -0
  80. ntc/templates/nextjs/src/app/globals.css +26 -0
  81. ntc/templates/nextjs/src/app/layout.tsx +59 -0
  82. ntc/templates/nextjs/src/app/page.tsx +21 -0
  83. ntc/templates/nextjs/src/components/Header.tsx +16 -0
  84. ntc/templates/nextjs/src/services/api.ts +81 -0
  85. ntc/templates/nextjs/src/utils/constants.ts +1 -0
  86. ntc/templates/nextjs/structure.json +32 -0
  87. ntc/templates/nextjs/tsconfig.json +27 -0
  88. ntc/templates/react_admin/.env.example +1 -0
  89. ntc/templates/react_admin/.gitignore +10 -0
  90. ntc/templates/react_admin/CLAUDE.md +210 -0
  91. ntc/templates/react_admin/README.md +72 -0
  92. ntc/templates/react_admin/__init__.py +0 -0
  93. ntc/templates/react_admin/index.html +13 -0
  94. ntc/templates/react_admin/knowledge/knowledge_description.md +202 -0
  95. ntc/templates/react_admin/knowledge/knowledge_design_pattern.md +448 -0
  96. ntc/templates/react_admin/knowledge/knowledge_structure.md +97 -0
  97. ntc/templates/react_admin/main.py +208 -0
  98. ntc/templates/react_admin/package.json +35 -0
  99. ntc/templates/react_admin/src/App.tsx +60 -0
  100. ntc/templates/react_admin/src/components/ProtectedRoute.tsx +16 -0
  101. ntc/templates/react_admin/src/components/Sidebar.tsx +90 -0
  102. ntc/templates/react_admin/src/components/TopNav.tsx +44 -0
  103. ntc/templates/react_admin/src/context/AuthContext.tsx +107 -0
  104. ntc/templates/react_admin/src/context/ThemeContext.tsx +34 -0
  105. ntc/templates/react_admin/src/context/WorkspaceContext.tsx +28 -0
  106. ntc/templates/react_admin/src/hooks/useApi.ts +39 -0
  107. ntc/templates/react_admin/src/index.css +34 -0
  108. ntc/templates/react_admin/src/layouts/AuthLayout.tsx +23 -0
  109. ntc/templates/react_admin/src/layouts/DashboardLayout.tsx +24 -0
  110. ntc/templates/react_admin/src/main.tsx +5 -0
  111. ntc/templates/react_admin/src/pages/DashboardPage.tsx +10 -0
  112. ntc/templates/react_admin/src/pages/auth/LoginPage.tsx +79 -0
  113. ntc/templates/react_admin/src/pages/auth/RegisterPage.tsx +93 -0
  114. ntc/templates/react_admin/src/services/api.service.ts +44 -0
  115. ntc/templates/react_admin/src/services/base.service.ts +193 -0
  116. ntc/templates/react_admin/structure.json +52 -0
  117. ntc/templates/react_admin/tsconfig.app.json +25 -0
  118. ntc/templates/react_admin/tsconfig.json +7 -0
  119. ntc/templates/react_admin/tsconfig.node.json +19 -0
  120. ntc/templates/react_admin/vite.config.ts +13 -0
  121. ntc/templates/react_structure/CLAUDE.md +176 -0
  122. ntc/templates/react_structure/__init__.py +1 -0
  123. ntc/templates/react_structure/index.css +21 -0
  124. ntc/templates/react_structure/index.html +13 -0
  125. ntc/templates/react_structure/knowledge/knowledge_description.md +170 -0
  126. ntc/templates/react_structure/knowledge/knowledge_design_pattern.md +411 -0
  127. ntc/templates/react_structure/knowledge/knowledge_structure.md +74 -0
  128. ntc/templates/react_structure/main.py +217 -0
  129. ntc/templates/react_structure/package.json +32 -0
  130. ntc/templates/react_structure/src/App.tsx +63 -0
  131. ntc/templates/react_structure/src/components/Header.tsx +51 -0
  132. ntc/templates/react_structure/src/main.tsx +5 -0
  133. ntc/templates/react_structure/src/services/api.ts +90 -0
  134. ntc/templates/react_structure/src/utils/constants.ts +1 -0
  135. ntc/templates/react_structure/structure.json +65 -0
  136. ntc/templates/react_structure/tsconfig.app.json +25 -0
  137. ntc/templates/react_structure/tsconfig.json +7 -0
  138. ntc/templates/react_structure/tsconfig.node.json +19 -0
  139. ntc/templates/react_structure/vite.config.ts +12 -0
  140. ntc/utils/__init__.py +1 -0
  141. ntc/utils/environment.py +67 -0
  142. ntc/utils/output.py +41 -0
  143. ntc_dev_kit-1.0.0.dist-info/METADATA +188 -0
  144. ntc_dev_kit-1.0.0.dist-info/RECORD +148 -0
  145. ntc_dev_kit-1.0.0.dist-info/WHEEL +5 -0
  146. ntc_dev_kit-1.0.0.dist-info/entry_points.txt +2 -0
  147. ntc_dev_kit-1.0.0.dist-info/licenses/LICENSE +21 -0
  148. ntc_dev_kit-1.0.0.dist-info/top_level.txt +1 -0
File without changes
ntc/composer/main.py ADDED
@@ -0,0 +1,443 @@
1
+ import os
2
+ import sys
3
+ import yaml
4
+ import msvcrt
5
+ import shutil
6
+ from colorama import Fore, Style
7
+ from ntc.utils.output import print_success, print_error, print_warning, print_info
8
+
9
+ def is_directory_empty(directory):
10
+ """Check if a directory is empty (ignoring hidden files)."""
11
+ if not os.path.exists(directory):
12
+ return True
13
+
14
+ items = [item for item in os.listdir(directory) if not item.startswith('.')]
15
+ return len(items) == 0
16
+
17
+ def get_project_choices():
18
+ """Get user choices for which projects to create using interactive menu."""
19
+ options = [
20
+ {"label": "Backend (Django REST Framework)", "value": "backend", "selected": False},
21
+ {"label": "Website (Next.js)", "value": "website", "selected": False},
22
+ {"label": "Mobile App (Flutter)", "value": "mobile", "selected": False},
23
+ {"label": "Admin Dashboard (React Admin)", "value": "admin", "selected": False},
24
+ {"label": "All of the above", "value": "all", "selected": False},
25
+ ]
26
+
27
+ current_index = 0
28
+
29
+ while True:
30
+ # Clear screen and redraw menu
31
+ os.system('cls' if os.name == 'nt' else 'clear')
32
+ print_info("Select the projects you want to create:")
33
+ print("─" * 50)
34
+ print("Use ↑↓ arrows to navigate, SPACE to select/deselect, ENTER to confirm")
35
+ print("─" * 50)
36
+
37
+ for i, option in enumerate(options):
38
+ prefix = "→ " if i == current_index else " "
39
+ checkmark = "✓" if option["selected"] else " "
40
+ label = option["label"]
41
+
42
+ if i == current_index:
43
+ print(f"{Fore.GREEN}{prefix}[{checkmark}] {label}{Style.RESET_ALL}")
44
+ else:
45
+ print(f"{prefix}[{checkmark}] {label}")
46
+
47
+ # Get key press
48
+ key = msvcrt.getch()
49
+
50
+ if key == b'\r': # Enter key
51
+ selected_projects = []
52
+ for option in options:
53
+ if option["selected"]:
54
+ if option["value"] == "all":
55
+ selected_projects = ["backend", "website", "mobile", "admin"]
56
+ break
57
+ else:
58
+ selected_projects.append(option["value"])
59
+
60
+ if not selected_projects:
61
+ print_warning("No projects selected. Defaulting to backend only.")
62
+ return ["backend"]
63
+
64
+ return selected_projects
65
+
66
+ elif key == b' ': # Space bar
67
+ if options[current_index]["value"] == "all":
68
+ # Toggle "All" option
69
+ options[current_index]["selected"] = not options[current_index]["selected"]
70
+ if options[current_index]["selected"]:
71
+ # Select all other options
72
+ for i in range(len(options) - 1):
73
+ options[i]["selected"] = True
74
+ else:
75
+ # Deselect all other options
76
+ for i in range(len(options) - 1):
77
+ options[i]["selected"] = False
78
+ else:
79
+ # Toggle individual option
80
+ options[current_index]["selected"] = not options[current_index]["selected"]
81
+ # If any individual option is deselected, deselect "All"
82
+ if not options[current_index]["selected"]:
83
+ options[-1]["selected"] = False
84
+ # If all individual options are selected, select "All"
85
+ elif all(options[i]["selected"] for i in range(len(options) - 1)):
86
+ options[-1]["selected"] = True
87
+
88
+ elif key == b'\x00' or key == b'\xe0': # Arrow key prefix
89
+ key = msvcrt.getch()
90
+ if key == b'H': # Up arrow
91
+ current_index = (current_index - 1) % len(options)
92
+ elif key == b'P': # Down arrow
93
+ current_index = (current_index + 1) % len(options)
94
+
95
+ def create_ntc_yml(project_name, projects, root_dir):
96
+ """Create ntc.yml configuration file."""
97
+ config = {
98
+ "project": {
99
+ "name": project_name,
100
+ "version": "1.0.0",
101
+ "description": f"{project_name} - Full stack application"
102
+ },
103
+ "components": {},
104
+ "deployment": {
105
+ "platform": "caprover",
106
+ "environment": "production"
107
+ }
108
+ }
109
+
110
+ # Add component configurations
111
+ if "backend" in projects:
112
+ config["components"]["backend"] = {
113
+ "type": "django",
114
+ "path": f"{project_name}_backend",
115
+ "port": 8000,
116
+ "environment": {
117
+ "type": "venv", # Default to venv, will be updated by user
118
+ "path": f"{project_name}_backend/env"
119
+ },
120
+ "dependencies": {
121
+ "file": "requirements.txt",
122
+ "manager": "pip"
123
+ },
124
+ "start_command": "python manage.py runserver"
125
+ }
126
+
127
+ if "website" in projects:
128
+ config["components"]["website"] = {
129
+ "type": "nextjs",
130
+ "path": f"{project_name}_website",
131
+ "port": 3000,
132
+ "environment": {
133
+ "type": "node",
134
+ "path": None
135
+ },
136
+ "dependencies": {
137
+ "file": "package.json",
138
+ "manager": "npm"
139
+ },
140
+ "start_command": "npm run dev"
141
+ }
142
+
143
+ if "mobile" in projects:
144
+ config["components"]["mobile"] = {
145
+ "type": "flutter",
146
+ "path": f"{project_name}_mobile",
147
+ "port": None,
148
+ "environment": {
149
+ "type": "flutter",
150
+ "path": None
151
+ },
152
+ "dependencies": {
153
+ "file": "pubspec.yaml",
154
+ "manager": "flutter"
155
+ },
156
+ "start_command": "flutter run"
157
+ }
158
+
159
+ if "admin" in projects:
160
+ config["components"]["admin"] = {
161
+ "type": "react-admin",
162
+ "path": f"{project_name}_admin",
163
+ "port": 5173,
164
+ "environment": {
165
+ "type": "node",
166
+ "path": None
167
+ },
168
+ "dependencies": {
169
+ "file": "package.json",
170
+ "manager": "npm"
171
+ },
172
+ "start_command": "npm run dev"
173
+ }
174
+
175
+ # Write ntc.yml
176
+ ntc_yml_path = os.path.join(root_dir, "ntc.yml")
177
+ with open(ntc_yml_path, "w") as f:
178
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
179
+
180
+ print_success(f"Created ntc.yml configuration at: {ntc_yml_path}")
181
+
182
+ def create_project(project_type, project_name, root_dir):
183
+ """Create a single project using the appropriate template."""
184
+ from ntc.templates.drf_structure.main import init_drf_project
185
+ from ntc.templates.nextjs.main import init_nextjs_project
186
+ from ntc.templates.flutter.main import init_flutter_project
187
+ from ntc.templates.react_admin.main import init_react_admin_project
188
+
189
+ project_postfix_map = {
190
+ "backend": "_backend",
191
+ "website": "_website",
192
+ "mobile": "_mobile",
193
+ "admin": "_admin"
194
+ }
195
+
196
+ project_postfix = project_postfix_map[project_type]
197
+ full_project_name = f"{project_name}{project_postfix}"
198
+ project_path = os.path.join(root_dir, full_project_name)
199
+
200
+ print_info(f"Creating {project_type} project: {full_project_name}")
201
+
202
+ try:
203
+ if project_type == "backend":
204
+ init_drf_project(root_dir, full_project_name)
205
+ elif project_type == "website":
206
+ init_nextjs_project(root_dir, full_project_name)
207
+ elif project_type == "mobile":
208
+ init_flutter_project(root_dir, full_project_name)
209
+ elif project_type == "admin":
210
+ init_react_admin_project(root_dir, full_project_name)
211
+
212
+ print_success(f"Successfully created {project_type} project: {full_project_name}")
213
+ return True
214
+ except Exception as e:
215
+ print_error(f"Failed to create {project_type} project: {e}")
216
+ return False
217
+
218
+ def compose_project(project_name, root_dir=None):
219
+ """Compose a full-stack project with multiple components."""
220
+ if root_dir is None:
221
+ root_dir = os.getcwd()
222
+
223
+ # Check if directory is empty
224
+ if not is_directory_empty(root_dir):
225
+ print_error("Directory is not empty. The composer requires an empty directory.")
226
+ print_info("Please run this command in an empty directory.")
227
+ sys.exit(1)
228
+
229
+ print_info(f"🎵 Composing project: {project_name}")
230
+ print_info(f"Root directory: {root_dir}")
231
+ print()
232
+
233
+ # Get project choices
234
+ projects = get_project_choices()
235
+
236
+ if not projects:
237
+ print_error("No projects selected. Exiting.")
238
+ sys.exit(1)
239
+
240
+ # Clear screen after project selection
241
+ os.system('cls' if os.name == 'nt' else 'clear')
242
+ print_info(f"Selected projects: {', '.join(projects)}")
243
+ print()
244
+
245
+ # Create each project
246
+ successful_projects = []
247
+ failed_projects = []
248
+
249
+ for project_type in projects:
250
+ if create_project(project_type, project_name, root_dir):
251
+ successful_projects.append(project_type)
252
+ else:
253
+ failed_projects.append(project_type)
254
+
255
+ # Create ntc.yml configuration
256
+ if successful_projects:
257
+ create_ntc_yml(project_name, successful_projects, root_dir)
258
+
259
+ # Clear screen before showing summary
260
+ os.system('cls' if os.name == 'nt' else 'clear')
261
+
262
+ # Summary
263
+ print("─" * 50)
264
+ print(f"{Fore.GREEN}COMPOSER SUMMARY{Style.RESET_ALL}")
265
+ print("─" * 50)
266
+
267
+ if successful_projects:
268
+ print_success(f"Successfully created: {', '.join(successful_projects)}")
269
+ if failed_projects:
270
+ print_error(f"Failed to create: {', '.join(failed_projects)}")
271
+
272
+ print()
273
+ print_info("Project structure:")
274
+ for project_type in successful_projects:
275
+ project_postfix_map = {
276
+ "backend": "_backend",
277
+ "website": "_website",
278
+ "mobile": "_mobile",
279
+ "admin": "_admin"
280
+ }
281
+ postfix = project_postfix_map[project_type]
282
+ print(f" 📁 {project_name}{postfix}")
283
+
284
+ print()
285
+ print_info(f"Configuration file: ntc.yml")
286
+ print_info("Use this file to configure deployment and project settings.")
287
+ print()
288
+ print_success("🎉 Project composition complete!")
289
+
290
+ def compose_project_in_current_dir(project_name):
291
+ """Compose a full-stack project in the current directory."""
292
+ compose_project(project_name, os.getcwd())
293
+
294
+ def start_services(root_dir=None):
295
+ """Start all services defined in ntc.yml configuration in terminal splits."""
296
+ if root_dir is None:
297
+ root_dir = os.getcwd()
298
+
299
+ ntc_yml_path = os.path.join(root_dir, "ntc.yml")
300
+
301
+ if not os.path.exists(ntc_yml_path):
302
+ print_error("ntc.yml not found. Are you in a project root directory?")
303
+ return
304
+
305
+ with open(ntc_yml_path, "r") as f:
306
+ config = yaml.safe_load(f)
307
+
308
+ components = config.get("components", {})
309
+ project_name = config.get("project", {}).get("name", "project")
310
+
311
+ if not components:
312
+ print_warning("No components found in ntc.yml configuration.")
313
+ return
314
+
315
+ print_info(f"Starting services for {project_name}...")
316
+ print()
317
+
318
+ # Check if Windows Terminal or tmux is available
319
+ wt_available = shutil.which("wt") is not None
320
+ tmux_available = shutil.which("tmux") is not None
321
+
322
+ # Prepare commands with environment activation and dependency installation
323
+ prepared_commands = []
324
+
325
+ for component_name, component_config in components.items():
326
+ component_type = component_config.get("type")
327
+ component_path = os.path.join(root_dir, component_config.get("path"))
328
+
329
+ if not os.path.exists(component_path):
330
+ print_warning(f"Component path not found: {component_path}")
331
+ continue
332
+
333
+ print_info(f"Preparing {component_name} ({component_type})...")
334
+
335
+ # Build command with environment activation
336
+ env_config = component_config.get("environment", {})
337
+ env_type = env_config.get("type", "none")
338
+ env_path = env_config.get("path")
339
+
340
+ # Check and install dependencies
341
+ dep_config = component_config.get("dependencies", {})
342
+ dep_file = dep_config.get("file")
343
+ dep_manager = dep_config.get("manager")
344
+
345
+ install_command = ""
346
+ if dep_file and dep_manager:
347
+ dep_file_path = os.path.join(component_path, dep_file)
348
+ if os.path.exists(dep_file_path):
349
+ print_info(f"Checking dependencies for {component_name}...")
350
+ # Add dependency installation check to command
351
+ if dep_manager == "pip":
352
+ install_command = f"pip install -r {dep_file}"
353
+ elif dep_manager == "npm":
354
+ install_command = f"npm install --legacy-peer-deps"
355
+ elif dep_manager == "flutter":
356
+ install_command = f"flutter pub get"
357
+ else:
358
+ print_warning(f"Dependency file not found: {dep_file_path}")
359
+
360
+ # Build start command with environment activation and dependency installation
361
+ if env_type == "venv" and env_path:
362
+ env_full_path = os.path.join(root_dir, env_path)
363
+ activate_cmd = f"{env_full_path}\\Scripts\\activate" if os.name == 'nt' else f"source {env_full_path}/bin/activate"
364
+ if install_command:
365
+ start_command = f"cd {component_path} && {activate_cmd} && {install_command} && {component_config.get('start_command', '')}"
366
+ else:
367
+ start_command = f"cd {component_path} && {activate_cmd} && {component_config.get('start_command', '')}"
368
+ elif env_type == "conda" and env_path:
369
+ activate_cmd = f"conda activate {env_path}"
370
+ if install_command:
371
+ start_command = f"cd {component_path} && {activate_cmd} && {install_command} && {component_config.get('start_command', '')}"
372
+ else:
373
+ start_command = f"cd {component_path} && {activate_cmd} && {component_config.get('start_command', '')}"
374
+ elif env_type == "node":
375
+ if install_command:
376
+ start_command = f"cd {component_path} && {install_command} && {component_config.get('start_command', '')}"
377
+ else:
378
+ start_command = f"cd {component_path} && {component_config.get('start_command', '')}"
379
+ elif env_type == "flutter":
380
+ if install_command:
381
+ start_command = f"cd {component_path} && {install_command} && {component_config.get('start_command', '')}"
382
+ else:
383
+ start_command = f"cd {component_path} && {component_config.get('start_command', '')}"
384
+ else:
385
+ if install_command:
386
+ start_command = f"cd {component_path} && {install_command} && {component_config.get('start_command', '')}"
387
+ else:
388
+ start_command = f"cd {component_path} && {component_config.get('start_command', '')}"
389
+
390
+ prepared_commands.append(start_command)
391
+
392
+ # Launch services
393
+ if tmux_available:
394
+ # Use tmux for better pane management
395
+ print_info("Using tmux for terminal splitting...")
396
+
397
+ # Start a new tmux session
398
+ session_name = f"{project_name}_services"
399
+ os.system(f"tmux kill-session -t {session_name} 2>/dev/null")
400
+ os.system(f"tmux new-session -d -s {session_name} -c {root_dir}")
401
+
402
+ for i, start_command in enumerate(prepared_commands):
403
+ if i == 0:
404
+ # First pane already exists
405
+ os.system(f"tmux send-keys -t {session_name}:0 '{start_command}' C-m")
406
+ elif i == 1:
407
+ # Split vertically for second pane
408
+ os.system(f"tmux split-window -t {session_name}:0 -v -c {root_dir}")
409
+ os.system(f"tmux send-keys -t {session_name}:0.1 '{start_command}' C-m")
410
+ elif i == 2:
411
+ # Split the left pane horizontally for third pane
412
+ os.system(f"tmux split-window -t {session_name}:0.0 -h -c {root_dir}")
413
+ os.system(f"tmux send-keys -t {session_name}:0.2 '{start_command}' C-m")
414
+ elif i == 3:
415
+ # Split the right pane horizontally for fourth pane
416
+ os.system(f"tmux split-window -t {session_name}:0.1 -h -c {root_dir}")
417
+ os.system(f"tmux send-keys -t {session_name}:0.3 '{start_command}' C-m")
418
+
419
+ # Attach to the tmux session
420
+ os.system(f"tmux attach-session -t {session_name}")
421
+ print_success(f"Launched all services in tmux with 2x2 grid layout")
422
+ elif wt_available:
423
+ # Use Windows Terminal with pane splits
424
+ wt_command = f"wt cmd /k \"{prepared_commands[0]}\""
425
+
426
+ for i, command in enumerate(prepared_commands[1:], 1):
427
+ if i == 1:
428
+ wt_command += f" ; split-pane -V cmd /k \"{command}\""
429
+ else:
430
+ wt_command += f" ; split-pane -H cmd /k \"{command}\""
431
+
432
+ os.system(wt_command)
433
+ print_success(f"Launched all services in Windows Terminal with split panes")
434
+ else:
435
+ # Fallback to opening new terminal windows
436
+ print_warning("Windows Terminal not found. Opening new terminal windows instead.")
437
+ print_info("Install Windows Terminal for pane splitting support.")
438
+
439
+ for start_command in prepared_commands:
440
+ os.system(f"start cmd /k \"{start_command}\"")
441
+
442
+ print()
443
+ print_success(f"All services started for {project_name}")
File without changes
@@ -0,0 +1,2 @@
1
+ from .drf_structure import init_drf_project
2
+ from .react_structure import init_react_project
File without changes
@@ -0,0 +1,24 @@
1
+ from django.contrib import admin
2
+ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
3
+ from .models import User
4
+
5
+
6
+ @admin.register(User)
7
+ class UserAdmin(BaseUserAdmin):
8
+ """Custom User admin."""
9
+ list_display = ('email', 'username', 'first_name', 'last_name', 'is_verified', 'is_staff', 'date_joined')
10
+ list_filter = ('is_staff', 'is_superuser', 'is_active', 'is_verified', 'date_joined')
11
+ search_fields = ('email', 'username', 'first_name', 'last_name')
12
+ ordering = ('-date_joined',)
13
+
14
+ fieldsets = BaseUserAdmin.fieldsets + (
15
+ ('Additional Info', {
16
+ 'fields': ('phone_number', 'date_of_birth', 'profile_picture', 'is_verified')
17
+ }),
18
+ )
19
+
20
+ add_fieldsets = BaseUserAdmin.add_fieldsets + (
21
+ ('Additional Info', {
22
+ 'fields': ('email', 'first_name', 'last_name')
23
+ }),
24
+ )
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class AuthenticationConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'authentication'
@@ -0,0 +1,30 @@
1
+ from django.contrib.auth.models import AbstractUser
2
+ from django.db import models
3
+
4
+
5
+ class User(AbstractUser):
6
+ """Custom User model extending AbstractUser."""
7
+ email = models.EmailField(unique=True)
8
+ first_name = models.CharField(max_length=30, blank=True)
9
+ last_name = models.CharField(max_length=30, blank=True)
10
+ phone_number = models.CharField(max_length=15, blank=True)
11
+ date_of_birth = models.DateField(null=True, blank=True)
12
+ profile_picture = models.ImageField(upload_to='profile_pics/', null=True, blank=True)
13
+ is_verified = models.BooleanField(default=False)
14
+ created_at = models.DateTimeField(auto_now_add=True)
15
+ updated_at = models.DateTimeField(auto_now=True)
16
+
17
+ USERNAME_FIELD = 'email'
18
+ REQUIRED_FIELDS = ['username']
19
+
20
+ class Meta:
21
+ db_table = 'auth_user'
22
+ verbose_name = 'User'
23
+ verbose_name_plural = 'Users'
24
+
25
+ def __str__(self):
26
+ return self.email
27
+
28
+ @property
29
+ def full_name(self):
30
+ return f"{self.first_name} {self.last_name}".strip()
@@ -0,0 +1,101 @@
1
+ from rest_framework import serializers
2
+ from django.contrib.auth import authenticate
3
+ from django.contrib.auth.password_validation import validate_password
4
+ from .models import User
5
+
6
+
7
+ class UserRegistrationSerializer(serializers.ModelSerializer):
8
+ """Serializer for user registration."""
9
+ password = serializers.CharField(write_only=True, validators=[validate_password])
10
+ password_confirm = serializers.CharField(write_only=True)
11
+
12
+ class Meta:
13
+ model = User
14
+ fields = ('email', 'username', 'password', 'password_confirm', 'first_name', 'last_name')
15
+
16
+ def validate(self, attrs):
17
+ if attrs['password'] != attrs['password_confirm']:
18
+ raise serializers.ValidationError("Passwords don't match.")
19
+ return attrs
20
+
21
+ def create(self, validated_data):
22
+ validated_data.pop('password_confirm')
23
+ user = User.objects.create_user(**validated_data)
24
+ return user
25
+
26
+
27
+ class UserLoginSerializer(serializers.Serializer):
28
+ """Serializer for user login."""
29
+ email = serializers.EmailField()
30
+ password = serializers.CharField(write_only=True)
31
+
32
+ def validate(self, attrs):
33
+ email = attrs.get('email')
34
+ password = attrs.get('password')
35
+
36
+ if email and password:
37
+ user = authenticate(username=email, password=password)
38
+ if not user:
39
+ raise serializers.ValidationError('Invalid credentials.')
40
+ if not user.is_active:
41
+ raise serializers.ValidationError('User account is disabled.')
42
+ attrs['user'] = user
43
+ else:
44
+ raise serializers.ValidationError('Must include email and password.')
45
+
46
+ return attrs
47
+
48
+
49
+ class UserProfileSerializer(serializers.ModelSerializer):
50
+ """Serializer for user profile."""
51
+ full_name = serializers.ReadOnlyField()
52
+
53
+ class Meta:
54
+ model = User
55
+ fields = ('id', 'email', 'username', 'first_name', 'last_name', 'full_name',
56
+ 'phone_number', 'date_of_birth', 'profile_picture', 'is_verified',
57
+ 'date_joined', 'created_at', 'updated_at')
58
+ read_only_fields = ('id', 'email', 'date_joined', 'created_at', 'updated_at', 'is_verified')
59
+
60
+
61
+ class UpdateProfileSerializer(serializers.ModelSerializer):
62
+ """Serializer for updating user profile."""
63
+
64
+ class Meta:
65
+ model = User
66
+ fields = ('first_name', 'last_name', 'phone_number', 'date_of_birth', 'profile_picture')
67
+
68
+
69
+ class ChangePasswordSerializer(serializers.Serializer):
70
+ """Serializer for changing password."""
71
+ old_password = serializers.CharField(write_only=True)
72
+ new_password = serializers.CharField(write_only=True, validators=[validate_password])
73
+ new_password_confirm = serializers.CharField(write_only=True)
74
+
75
+ def validate(self, attrs):
76
+ if attrs['new_password'] != attrs['new_password_confirm']:
77
+ raise serializers.ValidationError("New passwords don't match.")
78
+ return attrs
79
+
80
+ def validate_old_password(self, value):
81
+ user = self.context['request'].user
82
+ if not user.check_password(value):
83
+ raise serializers.ValidationError("Old password is incorrect.")
84
+ return value
85
+
86
+
87
+ class ResetPasswordSerializer(serializers.Serializer):
88
+ """Serializer for password reset request."""
89
+ email = serializers.EmailField()
90
+
91
+
92
+ class ResetPasswordConfirmSerializer(serializers.Serializer):
93
+ """Serializer for password reset confirmation."""
94
+ token = serializers.CharField()
95
+ new_password = serializers.CharField(write_only=True, validators=[validate_password])
96
+ new_password_confirm = serializers.CharField(write_only=True)
97
+
98
+ def validate(self, attrs):
99
+ if attrs['new_password'] != attrs['new_password_confirm']:
100
+ raise serializers.ValidationError("Passwords don't match.")
101
+ return attrs
@@ -0,0 +1,19 @@
1
+ from django.urls import path
2
+ from . import views
3
+
4
+
5
+ app_name = 'authentication'
6
+
7
+
8
+ urlpatterns = [
9
+ # Add your URL patterns here
10
+ path('login/', views.login, name='login'),
11
+ path('register/', views.register, name='register'),
12
+ path('logout/', views.logout, name='logout'),
13
+ path('refresh/', views.refresh, name='refresh'),
14
+ path('me/', views.me, name='me'),
15
+ path('update-profile/', views.update_profile, name='update_profile'),
16
+ path('change-password/', views.change_password, name='change_password'),
17
+ path('reset-password/', views.reset_password, name='reset_password'),
18
+ path('reset-password-confirm/', views.reset_password_confirm, name='reset_password_confirm'),
19
+ ]