fastapi-fullstack 0.1.7__py3-none-any.whl → 0.1.15__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.
- {fastapi_fullstack-0.1.7.dist-info → fastapi_fullstack-0.1.15.dist-info}/METADATA +9 -2
- {fastapi_fullstack-0.1.7.dist-info → fastapi_fullstack-0.1.15.dist-info}/RECORD +71 -55
- fastapi_gen/__init__.py +6 -1
- fastapi_gen/cli.py +9 -0
- fastapi_gen/config.py +154 -2
- fastapi_gen/generator.py +34 -14
- fastapi_gen/prompts.py +172 -31
- fastapi_gen/template/VARIABLES.md +33 -4
- fastapi_gen/template/cookiecutter.json +10 -0
- fastapi_gen/template/hooks/post_gen_project.py +87 -2
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitlab-ci.yml +178 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +334 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +10 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +1 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/crewai_assistant.py +563 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/deepagents_assistant.py +526 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +4 -3
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langgraph_assistant.py +371 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +1472 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +3 -7
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +2 -2
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +7 -2
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +44 -7
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +262 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +76 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +118 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +158 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +185 -3
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +29 -2
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +6 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +4 -4
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +9 -9
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +6 -6
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +7 -7
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +1 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/arq_app.py +165 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +10 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +40 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_metrics.py +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +6 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +100 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +28 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +22 -4
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +23 -3
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-approval-dialog.tsx +138 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +242 -18
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +242 -17
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +1 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +57 -1
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/configmap.yaml +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/deployment.yaml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/ingress.yaml +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/kustomization.yaml +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/namespace.yaml +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/secret.yaml +59 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/service.yaml +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/nginx/nginx.conf +225 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/nginx/ssl/.gitkeep +18 -0
- {fastapi_fullstack-0.1.7.dist-info → fastapi_fullstack-0.1.15.dist-info}/WHEEL +0 -0
- {fastapi_fullstack-0.1.7.dist-info → fastapi_fullstack-0.1.15.dist-info}/entry_points.txt +0 -0
- {fastapi_fullstack-0.1.7.dist-info → fastapi_fullstack-0.1.15.dist-info}/licenses/LICENSE +0 -0
fastapi_gen/generator.py
CHANGED
|
@@ -12,31 +12,31 @@ from .config import DatabaseType, FrontendType, ProjectConfig
|
|
|
12
12
|
console = Console()
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def _get_database_setup_commands(database: DatabaseType) -> list[str]:
|
|
15
|
+
def _get_database_setup_commands(database: DatabaseType) -> list[tuple[str, str]]:
|
|
16
16
|
"""Get database-specific setup commands for post-generation messages.
|
|
17
17
|
|
|
18
18
|
Args:
|
|
19
19
|
database: The database type selected by the user
|
|
20
20
|
|
|
21
21
|
Returns:
|
|
22
|
-
List of command
|
|
22
|
+
List of (command, description) tuples to display
|
|
23
23
|
"""
|
|
24
24
|
if database == DatabaseType.SQLITE:
|
|
25
25
|
return [
|
|
26
|
-
"# SQLite database will be created automatically
|
|
27
|
-
"make db-migrate
|
|
28
|
-
"make db-upgrade
|
|
26
|
+
("# SQLite database will be created automatically", ""),
|
|
27
|
+
("make db-migrate", "Create initial migration"),
|
|
28
|
+
("make db-upgrade", "Apply migrations"),
|
|
29
29
|
]
|
|
30
30
|
elif database == DatabaseType.MONGODB:
|
|
31
31
|
return [
|
|
32
|
-
"make docker-mongo
|
|
33
|
-
"# Or configure MongoDB Atlas connection in .env",
|
|
32
|
+
("make docker-mongo", "Start MongoDB container"),
|
|
33
|
+
("# Or configure MongoDB Atlas connection in .env", ""),
|
|
34
34
|
]
|
|
35
35
|
else: # PostgreSQL
|
|
36
36
|
return [
|
|
37
|
-
"make docker-db
|
|
38
|
-
"make db-migrate
|
|
39
|
-
"make db-upgrade
|
|
37
|
+
("make docker-db", "Start PostgreSQL container"),
|
|
38
|
+
("make db-migrate", "Create initial migration"),
|
|
39
|
+
("make db-upgrade", "Apply migrations"),
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
|
|
@@ -153,8 +153,18 @@ def post_generation_tasks(project_path: Path, config: ProjectConfig) -> None:
|
|
|
153
153
|
if config.database.value != "none":
|
|
154
154
|
console.print()
|
|
155
155
|
console.print(f"[bold]{step}. Database setup:[/]")
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
db_commands = _get_database_setup_commands(config.database)
|
|
157
|
+
for cmd, desc in db_commands:
|
|
158
|
+
if desc:
|
|
159
|
+
console.print(f" {cmd:22}# {desc}")
|
|
160
|
+
else:
|
|
161
|
+
console.print(f" {cmd}")
|
|
162
|
+
if config.database != DatabaseType.MONGODB:
|
|
163
|
+
console.print()
|
|
164
|
+
console.print(
|
|
165
|
+
" [dim]⚠️ Run all commands in order:[/] "
|
|
166
|
+
"[dim]db-migrate creates the migration, db-upgrade applies it[/]"
|
|
167
|
+
)
|
|
158
168
|
step += 1
|
|
159
169
|
console.print()
|
|
160
170
|
console.print(f"[bold]{step}. Run backend:[/]")
|
|
@@ -184,8 +194,18 @@ def post_generation_tasks(project_path: Path, config: ProjectConfig) -> None:
|
|
|
184
194
|
if config.database.value != "none":
|
|
185
195
|
console.print()
|
|
186
196
|
console.print(f"[bold]{step}. Database setup:[/]")
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
db_commands = _get_database_setup_commands(config.database)
|
|
198
|
+
for cmd, desc in db_commands:
|
|
199
|
+
if desc:
|
|
200
|
+
console.print(f" {cmd:22}# {desc}")
|
|
201
|
+
else:
|
|
202
|
+
console.print(f" {cmd}")
|
|
203
|
+
if config.database != DatabaseType.MONGODB:
|
|
204
|
+
console.print()
|
|
205
|
+
console.print(
|
|
206
|
+
" [dim]⚠️ Run all commands in order:[/] "
|
|
207
|
+
"[dim]db-migrate creates the migration, db-upgrade applies it[/]"
|
|
208
|
+
)
|
|
189
209
|
step += 1
|
|
190
210
|
console.print()
|
|
191
211
|
console.print(f"[bold]{step}. Run server:[/]")
|
fastapi_gen/prompts.py
CHANGED
|
@@ -19,6 +19,7 @@ from .config import (
|
|
|
19
19
|
LLMProviderType,
|
|
20
20
|
LogfireFeatures,
|
|
21
21
|
OAuthProvider,
|
|
22
|
+
OrmType,
|
|
22
23
|
ProjectConfig,
|
|
23
24
|
RateLimitStorageType,
|
|
24
25
|
ReverseProxyType,
|
|
@@ -152,6 +153,31 @@ def prompt_database() -> DatabaseType:
|
|
|
152
153
|
)
|
|
153
154
|
|
|
154
155
|
|
|
156
|
+
def prompt_orm_type() -> OrmType:
|
|
157
|
+
"""Prompt for ORM library selection."""
|
|
158
|
+
choices = [
|
|
159
|
+
questionary.Choice(
|
|
160
|
+
"SQLAlchemy — full control, supports admin panel",
|
|
161
|
+
value=OrmType.SQLALCHEMY,
|
|
162
|
+
),
|
|
163
|
+
questionary.Choice(
|
|
164
|
+
"SQLModel — less boilerplate, no admin panel support",
|
|
165
|
+
value=OrmType.SQLMODEL,
|
|
166
|
+
),
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
return cast(
|
|
170
|
+
OrmType,
|
|
171
|
+
_check_cancelled(
|
|
172
|
+
questionary.select(
|
|
173
|
+
"ORM Library:",
|
|
174
|
+
choices=choices,
|
|
175
|
+
default=choices[0],
|
|
176
|
+
).ask()
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
155
181
|
def prompt_auth() -> AuthType:
|
|
156
182
|
"""Prompt for authentication method."""
|
|
157
183
|
console.print()
|
|
@@ -263,36 +289,120 @@ def prompt_background_tasks() -> BackgroundTaskType:
|
|
|
263
289
|
)
|
|
264
290
|
|
|
265
291
|
|
|
266
|
-
def prompt_integrations(
|
|
267
|
-
|
|
292
|
+
def prompt_integrations(
|
|
293
|
+
database: DatabaseType,
|
|
294
|
+
orm_type: OrmType,
|
|
295
|
+
) -> dict[str, bool]:
|
|
296
|
+
"""Prompt for optional integrations.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
database: Selected database type (affects which options are shown).
|
|
300
|
+
orm_type: Selected ORM type (SQLModel doesn't support admin panel).
|
|
301
|
+
"""
|
|
268
302
|
console.print()
|
|
269
303
|
console.print("[bold cyan]Optional Integrations[/]")
|
|
270
304
|
console.print()
|
|
271
305
|
|
|
306
|
+
# Build choices dynamically based on context
|
|
307
|
+
choices: list[questionary.Choice] = [
|
|
308
|
+
questionary.Choice(
|
|
309
|
+
"Redis — required for caching, rate limiting (Redis), task queues",
|
|
310
|
+
value="redis",
|
|
311
|
+
),
|
|
312
|
+
questionary.Choice(
|
|
313
|
+
"Caching (fastapi-cache2) — requires Redis",
|
|
314
|
+
value="caching",
|
|
315
|
+
),
|
|
316
|
+
questionary.Choice(
|
|
317
|
+
"Rate limiting (slowapi) — optional Redis storage",
|
|
318
|
+
value="rate_limiting",
|
|
319
|
+
),
|
|
320
|
+
questionary.Choice(
|
|
321
|
+
"Pagination (fastapi-pagination)",
|
|
322
|
+
value="pagination",
|
|
323
|
+
checked=True,
|
|
324
|
+
),
|
|
325
|
+
questionary.Choice(
|
|
326
|
+
"Sentry — error tracking & monitoring",
|
|
327
|
+
value="sentry",
|
|
328
|
+
),
|
|
329
|
+
questionary.Choice(
|
|
330
|
+
"Prometheus — metrics endpoint for monitoring",
|
|
331
|
+
value="prometheus",
|
|
332
|
+
),
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
# Admin Panel only available with SQLAlchemy (not SQLModel) and SQL database
|
|
336
|
+
if (
|
|
337
|
+
database in (DatabaseType.POSTGRESQL, DatabaseType.SQLITE)
|
|
338
|
+
and orm_type == OrmType.SQLALCHEMY
|
|
339
|
+
):
|
|
340
|
+
choices.append(
|
|
341
|
+
questionary.Choice(
|
|
342
|
+
"Admin Panel (SQLAdmin) — web UI for database management",
|
|
343
|
+
value="admin_panel",
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
choices.extend(
|
|
348
|
+
[
|
|
349
|
+
questionary.Choice(
|
|
350
|
+
"WebSockets — real-time bidirectional communication",
|
|
351
|
+
value="websockets",
|
|
352
|
+
),
|
|
353
|
+
questionary.Choice(
|
|
354
|
+
"File Storage (S3/MinIO) — file upload/download support",
|
|
355
|
+
value="file_storage",
|
|
356
|
+
),
|
|
357
|
+
questionary.Choice(
|
|
358
|
+
"AI Agent (PydanticAI/LangGraph/CrewAI) — LLM-powered assistant",
|
|
359
|
+
value="ai_agent",
|
|
360
|
+
checked=True,
|
|
361
|
+
),
|
|
362
|
+
]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Webhooks require database
|
|
366
|
+
if database != DatabaseType.NONE:
|
|
367
|
+
choices.append(
|
|
368
|
+
questionary.Choice(
|
|
369
|
+
"Webhooks — outbound event notifications",
|
|
370
|
+
value="webhooks",
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
choices.extend(
|
|
375
|
+
[
|
|
376
|
+
questionary.Choice(
|
|
377
|
+
"Example CRUD (Item model) — sample API endpoints",
|
|
378
|
+
value="example_crud",
|
|
379
|
+
checked=True,
|
|
380
|
+
),
|
|
381
|
+
questionary.Choice(
|
|
382
|
+
"CORS middleware — cross-origin request support",
|
|
383
|
+
value="cors",
|
|
384
|
+
checked=True,
|
|
385
|
+
),
|
|
386
|
+
questionary.Choice(
|
|
387
|
+
"orjson — faster JSON serialization",
|
|
388
|
+
value="orjson",
|
|
389
|
+
checked=True,
|
|
390
|
+
),
|
|
391
|
+
]
|
|
392
|
+
)
|
|
393
|
+
|
|
272
394
|
features = _check_cancelled(
|
|
273
395
|
questionary.checkbox(
|
|
274
396
|
"Select additional features:",
|
|
275
|
-
choices=
|
|
276
|
-
questionary.Choice("Redis (caching/sessions)", value="redis"),
|
|
277
|
-
questionary.Choice("Caching (fastapi-cache2)", value="caching"),
|
|
278
|
-
questionary.Choice("Rate limiting (slowapi)", value="rate_limiting"),
|
|
279
|
-
questionary.Choice(
|
|
280
|
-
"Pagination (fastapi-pagination)", value="pagination", checked=True
|
|
281
|
-
),
|
|
282
|
-
questionary.Choice("Sentry (error tracking)", value="sentry"),
|
|
283
|
-
questionary.Choice("Prometheus (metrics)", value="prometheus"),
|
|
284
|
-
questionary.Choice("Admin Panel (SQLAdmin)", value="admin_panel"),
|
|
285
|
-
questionary.Choice("WebSockets", value="websockets"),
|
|
286
|
-
questionary.Choice("File Storage (S3/MinIO)", value="file_storage"),
|
|
287
|
-
questionary.Choice("AI Agent (PydanticAI/LangChain)", value="ai_agent"),
|
|
288
|
-
questionary.Choice("Webhooks (outbound events)", value="webhooks"),
|
|
289
|
-
questionary.Choice("Example CRUD (Item model)", value="example_crud", checked=True),
|
|
290
|
-
questionary.Choice("CORS middleware", value="cors", checked=True),
|
|
291
|
-
questionary.Choice("orjson (faster JSON)", value="orjson", checked=True),
|
|
292
|
-
],
|
|
397
|
+
choices=choices,
|
|
293
398
|
).ask()
|
|
294
399
|
)
|
|
295
400
|
|
|
401
|
+
# Auto-enable Redis for caching (show info message)
|
|
402
|
+
if "caching" in features and "redis" not in features:
|
|
403
|
+
console.print("[yellow]ℹ Caching requires Redis — auto-enabled[/]")
|
|
404
|
+
features.append("redis")
|
|
405
|
+
|
|
296
406
|
return {
|
|
297
407
|
"enable_redis": "redis" in features,
|
|
298
408
|
"enable_caching": "caching" in features,
|
|
@@ -426,7 +536,14 @@ def prompt_reverse_proxy() -> ReverseProxyType:
|
|
|
426
536
|
"Traefik (included in docker-compose)", value=ReverseProxyType.TRAEFIK_INCLUDED
|
|
427
537
|
),
|
|
428
538
|
questionary.Choice(
|
|
429
|
-
"
|
|
539
|
+
"Traefik (external, shared between projects)",
|
|
540
|
+
value=ReverseProxyType.TRAEFIK_EXTERNAL,
|
|
541
|
+
),
|
|
542
|
+
questionary.Choice(
|
|
543
|
+
"Nginx (included in docker-compose)", value=ReverseProxyType.NGINX_INCLUDED
|
|
544
|
+
),
|
|
545
|
+
questionary.Choice(
|
|
546
|
+
"Nginx (external, config template only)", value=ReverseProxyType.NGINX_EXTERNAL
|
|
430
547
|
),
|
|
431
548
|
questionary.Choice(
|
|
432
549
|
"None (expose ports directly, use own proxy)", value=ReverseProxyType.NONE
|
|
@@ -452,8 +569,8 @@ def prompt_frontend() -> FrontendType:
|
|
|
452
569
|
console.print()
|
|
453
570
|
|
|
454
571
|
choices = [
|
|
455
|
-
questionary.Choice("None (API only)", value=FrontendType.NONE),
|
|
456
572
|
questionary.Choice("Next.js 15 (App Router, TypeScript, Bun)", value=FrontendType.NEXTJS),
|
|
573
|
+
questionary.Choice("None (API only)", value=FrontendType.NONE),
|
|
457
574
|
]
|
|
458
575
|
|
|
459
576
|
return cast(
|
|
@@ -497,6 +614,9 @@ def prompt_ai_framework() -> AIFrameworkType:
|
|
|
497
614
|
choices = [
|
|
498
615
|
questionary.Choice("PydanticAI (recommended)", value=AIFrameworkType.PYDANTIC_AI),
|
|
499
616
|
questionary.Choice("LangChain", value=AIFrameworkType.LANGCHAIN),
|
|
617
|
+
questionary.Choice("LangGraph (ReAct agent)", value=AIFrameworkType.LANGGRAPH),
|
|
618
|
+
questionary.Choice("CrewAI (multi-agent crews)", value=AIFrameworkType.CREWAI),
|
|
619
|
+
questionary.Choice("DeepAgents (agentic coding)", value=AIFrameworkType.DEEPAGENTS),
|
|
500
620
|
]
|
|
501
621
|
|
|
502
622
|
return cast(
|
|
@@ -516,7 +636,7 @@ def prompt_llm_provider(ai_framework: AIFrameworkType) -> LLMProviderType:
|
|
|
516
636
|
|
|
517
637
|
Args:
|
|
518
638
|
ai_framework: The selected AI framework. OpenRouter is only
|
|
519
|
-
available for PydanticAI.
|
|
639
|
+
available for PydanticAI (not LangChain, LangGraph, CrewAI, or DeepAgents).
|
|
520
640
|
"""
|
|
521
641
|
console.print()
|
|
522
642
|
console.print("[bold cyan]LLM Provider[/]")
|
|
@@ -527,7 +647,7 @@ def prompt_llm_provider(ai_framework: AIFrameworkType) -> LLMProviderType:
|
|
|
527
647
|
questionary.Choice("Anthropic (claude-sonnet-4-5)", value=LLMProviderType.ANTHROPIC),
|
|
528
648
|
]
|
|
529
649
|
|
|
530
|
-
# OpenRouter only available for PydanticAI
|
|
650
|
+
# OpenRouter only available for PydanticAI (not LangChain, LangGraph, or CrewAI)
|
|
531
651
|
if ai_framework == AIFrameworkType.PYDANTIC_AI:
|
|
532
652
|
choices.append(
|
|
533
653
|
questionary.Choice("OpenRouter (multi-provider)", value=LLMProviderType.OPENROUTER)
|
|
@@ -545,18 +665,31 @@ def prompt_llm_provider(ai_framework: AIFrameworkType) -> LLMProviderType:
|
|
|
545
665
|
)
|
|
546
666
|
|
|
547
667
|
|
|
548
|
-
def prompt_websocket_auth() -> WebSocketAuthType:
|
|
549
|
-
"""Prompt for WebSocket authentication method for AI Agent.
|
|
668
|
+
def prompt_websocket_auth(auth: AuthType) -> WebSocketAuthType:
|
|
669
|
+
"""Prompt for WebSocket authentication method for AI Agent.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
auth: The main auth type. JWT WebSocket auth is only available
|
|
673
|
+
if JWT is enabled (JWT or BOTH).
|
|
674
|
+
"""
|
|
550
675
|
console.print()
|
|
551
676
|
console.print("[bold cyan]AI Agent WebSocket Authentication[/]")
|
|
552
677
|
console.print()
|
|
553
678
|
|
|
554
679
|
choices = [
|
|
555
680
|
questionary.Choice("None (public access)", value=WebSocketAuthType.NONE),
|
|
556
|
-
questionary.Choice("JWT token required", value=WebSocketAuthType.JWT),
|
|
557
|
-
questionary.Choice("API Key required (query param)", value=WebSocketAuthType.API_KEY),
|
|
558
681
|
]
|
|
559
682
|
|
|
683
|
+
# JWT WebSocket auth only available if main auth uses JWT
|
|
684
|
+
if auth in (AuthType.JWT, AuthType.BOTH):
|
|
685
|
+
choices.append(questionary.Choice("JWT token required", value=WebSocketAuthType.JWT))
|
|
686
|
+
|
|
687
|
+
# API Key WebSocket auth available if main auth uses API keys
|
|
688
|
+
if auth in (AuthType.API_KEY, AuthType.BOTH):
|
|
689
|
+
choices.append(
|
|
690
|
+
questionary.Choice("API Key required (query param)", value=WebSocketAuthType.API_KEY)
|
|
691
|
+
)
|
|
692
|
+
|
|
560
693
|
return cast(
|
|
561
694
|
WebSocketAuthType,
|
|
562
695
|
_check_cancelled(
|
|
@@ -679,6 +812,11 @@ def run_interactive_prompts() -> ProjectConfig:
|
|
|
679
812
|
# Database
|
|
680
813
|
database = prompt_database()
|
|
681
814
|
|
|
815
|
+
# ORM type (only for PostgreSQL or SQLite)
|
|
816
|
+
orm_type = OrmType.SQLALCHEMY
|
|
817
|
+
if database in (DatabaseType.POSTGRESQL, DatabaseType.SQLITE):
|
|
818
|
+
orm_type = prompt_orm_type()
|
|
819
|
+
|
|
682
820
|
# Auth
|
|
683
821
|
auth = prompt_auth()
|
|
684
822
|
|
|
@@ -702,8 +840,8 @@ def run_interactive_prompts() -> ProjectConfig:
|
|
|
702
840
|
# Background tasks
|
|
703
841
|
background_tasks = prompt_background_tasks()
|
|
704
842
|
|
|
705
|
-
# Integrations
|
|
706
|
-
integrations = prompt_integrations()
|
|
843
|
+
# Integrations (pass context for dynamic option filtering)
|
|
844
|
+
integrations = prompt_integrations(database=database, orm_type=orm_type)
|
|
707
845
|
|
|
708
846
|
# Dev tools
|
|
709
847
|
dev_tools = prompt_dev_tools()
|
|
@@ -738,7 +876,7 @@ def run_interactive_prompts() -> ProjectConfig:
|
|
|
738
876
|
if integrations.get("enable_ai_agent"):
|
|
739
877
|
ai_framework = prompt_ai_framework()
|
|
740
878
|
llm_provider = prompt_llm_provider(ai_framework)
|
|
741
|
-
websocket_auth = prompt_websocket_auth()
|
|
879
|
+
websocket_auth = prompt_websocket_auth(auth=auth)
|
|
742
880
|
# Only offer persistence if database is enabled
|
|
743
881
|
if database != DatabaseType.NONE:
|
|
744
882
|
enable_conversation_persistence = _check_cancelled(
|
|
@@ -781,6 +919,7 @@ def run_interactive_prompts() -> ProjectConfig:
|
|
|
781
919
|
author_name=basic_info["author_name"],
|
|
782
920
|
author_email=basic_info["author_email"],
|
|
783
921
|
database=database,
|
|
922
|
+
orm_type=orm_type,
|
|
784
923
|
auth=auth,
|
|
785
924
|
oauth_provider=oauth_provider,
|
|
786
925
|
enable_session_management=enable_session_management,
|
|
@@ -818,6 +957,8 @@ def show_summary(config: ProjectConfig) -> None:
|
|
|
818
957
|
|
|
819
958
|
console.print(f" [cyan]Project:[/] {config.project_name}")
|
|
820
959
|
console.print(f" [cyan]Database:[/] {config.database.value}")
|
|
960
|
+
if config.database in (DatabaseType.POSTGRESQL, DatabaseType.SQLITE):
|
|
961
|
+
console.print(f" [cyan]ORM:[/] {config.orm_type.value}")
|
|
821
962
|
auth_str = config.auth.value
|
|
822
963
|
if config.oauth_provider != OAuthProvider.NONE:
|
|
823
964
|
auth_str += f" + {config.oauth_provider.value} OAuth"
|
|
@@ -59,6 +59,19 @@ These variables are set automatically by the generator.
|
|
|
59
59
|
| `db_max_overflow` | int | `10` | Max overflow connections above pool size | Requires SQL database |
|
|
60
60
|
| `db_pool_timeout` | int | `30` | Timeout (seconds) waiting for connection | Requires SQL database |
|
|
61
61
|
|
|
62
|
+
### ORM Library
|
|
63
|
+
|
|
64
|
+
| Variable | Type | Default | Description | Dependencies |
|
|
65
|
+
|----------|------|---------|-------------|--------------|
|
|
66
|
+
| `orm_type` | enum | `"sqlalchemy"` | ORM library. Values: `sqlalchemy`, `sqlmodel` | Requires SQL database |
|
|
67
|
+
| `use_sqlalchemy` | bool | `true` | SQLAlchemy is selected | Computed from `orm_type` |
|
|
68
|
+
| `use_sqlmodel` | bool | `false` | SQLModel is selected | Computed from `orm_type` |
|
|
69
|
+
|
|
70
|
+
**Notes:**
|
|
71
|
+
- SQLModel provides simplified syntax combining SQLAlchemy and Pydantic
|
|
72
|
+
- SQLModel is only available for PostgreSQL and SQLite (not MongoDB)
|
|
73
|
+
- SQLModel uses the same database session and migrations as SQLAlchemy
|
|
74
|
+
|
|
62
75
|
**Notes:**
|
|
63
76
|
- PostgreSQL uses `asyncpg` for async operations
|
|
64
77
|
- MongoDB uses `motor` for async operations
|
|
@@ -174,9 +187,12 @@ These variables are set automatically by the generator.
|
|
|
174
187
|
| Variable | Type | Default | Description | Dependencies |
|
|
175
188
|
|----------|------|---------|-------------|--------------|
|
|
176
189
|
| `enable_ai_agent` | bool | `false` | Enable AI agent functionality | - |
|
|
177
|
-
| `ai_framework` | enum | `"pydantic_ai"` | AI framework. Values: `pydantic_ai`, `langchain` | Requires `enable_ai_agent` |
|
|
190
|
+
| `ai_framework` | enum | `"pydantic_ai"` | AI framework. Values: `pydantic_ai`, `langchain`, `langgraph`, `crewai`, `deepagents` | Requires `enable_ai_agent` |
|
|
178
191
|
| `use_pydantic_ai` | bool | `true` | PydanticAI is selected | Computed from `ai_framework` |
|
|
179
192
|
| `use_langchain` | bool | `false` | LangChain is selected | Computed from `ai_framework` |
|
|
193
|
+
| `use_langgraph` | bool | `false` | LangGraph (ReAct agent) is selected | Computed from `ai_framework` |
|
|
194
|
+
| `use_crewai` | bool | `false` | CrewAI (multi-agent crews) is selected | Computed from `ai_framework` |
|
|
195
|
+
| `use_deepagents` | bool | `false` | DeepAgents (agentic coding) is selected | Computed from `ai_framework` |
|
|
180
196
|
| `llm_provider` | enum | `"openai"` | LLM provider. Values: `openai`, `anthropic`, `openrouter` | Requires `enable_ai_agent` |
|
|
181
197
|
| `use_openai` | bool | `true` | OpenAI is selected | Computed from `llm_provider` |
|
|
182
198
|
| `use_anthropic` | bool | `false` | Anthropic is selected | Computed from `llm_provider` |
|
|
@@ -185,7 +201,10 @@ These variables are set automatically by the generator.
|
|
|
185
201
|
|
|
186
202
|
**Notes:**
|
|
187
203
|
- PydanticAI uses `iter()` for full event streaming over WebSocket
|
|
188
|
-
-
|
|
204
|
+
- LangGraph implements a ReAct (Reasoning + Acting) agent pattern with graph-based architecture
|
|
205
|
+
- CrewAI enables multi-agent teams that collaborate on complex tasks
|
|
206
|
+
- DeepAgents provides an agentic coding assistant with built-in filesystem tools (ls, read_file, write_file, edit_file, glob, grep) and task management
|
|
207
|
+
- OpenRouter with LangChain, LangGraph, CrewAI, or DeepAgents is not supported
|
|
189
208
|
|
|
190
209
|
---
|
|
191
210
|
|
|
@@ -221,14 +240,20 @@ These variables are set automatically by the generator.
|
|
|
221
240
|
| `use_github_actions` | bool | `true` | GitHub Actions is selected | Computed from `ci_type` |
|
|
222
241
|
| `use_gitlab_ci` | bool | `false` | GitLab CI is selected | Computed from `ci_type` |
|
|
223
242
|
| `enable_kubernetes` | bool | `false` | Include Kubernetes manifests | - |
|
|
224
|
-
| `reverse_proxy` | enum | `"traefik_included"` | Reverse proxy config. Values: `traefik_included`, `traefik_external`, `none` | Requires Docker |
|
|
243
|
+
| `reverse_proxy` | enum | `"traefik_included"` | Reverse proxy config. Values: `traefik_included`, `traefik_external`, `nginx_included`, `nginx_external`, `none` | Requires Docker |
|
|
225
244
|
| `include_traefik_service` | bool | `true` | Include Traefik container in docker-compose | Computed from `reverse_proxy` |
|
|
226
245
|
| `include_traefik_labels` | bool | `true` | Include Traefik labels on services | Computed from `reverse_proxy` |
|
|
246
|
+
| `use_traefik` | bool | `true` | Using Traefik (included or external) | Computed from `reverse_proxy` |
|
|
247
|
+
| `include_nginx_service` | bool | `false` | Include Nginx container in docker-compose | Computed from `reverse_proxy` |
|
|
248
|
+
| `include_nginx_config` | bool | `false` | Generate nginx configuration files | Computed from `reverse_proxy` |
|
|
249
|
+
| `use_nginx` | bool | `false` | Using Nginx (included or external) | Computed from `reverse_proxy` |
|
|
227
250
|
|
|
228
251
|
**Reverse Proxy Options:**
|
|
229
252
|
- `traefik_included`: Full Traefik setup included in docker-compose.prod.yml (default)
|
|
230
253
|
- `traefik_external`: Services have Traefik labels but no Traefik container (for shared Traefik)
|
|
231
|
-
- `
|
|
254
|
+
- `nginx_included`: Full Nginx setup included in docker-compose.prod.yml with config template
|
|
255
|
+
- `nginx_external`: Nginx config template only, for external Nginx (no container in compose)
|
|
256
|
+
- `none`: No reverse proxy, ports exposed directly (use your own proxy)
|
|
232
257
|
|
|
233
258
|
---
|
|
234
259
|
|
|
@@ -265,6 +290,10 @@ database = "postgresql"
|
|
|
265
290
|
→ use_mongodb = false
|
|
266
291
|
→ use_sqlite = false
|
|
267
292
|
→ use_database = true
|
|
293
|
+
|
|
294
|
+
orm_type = "sqlmodel"
|
|
295
|
+
→ use_sqlalchemy = false
|
|
296
|
+
→ use_sqlmodel = true
|
|
268
297
|
```
|
|
269
298
|
|
|
270
299
|
These computed variables are used in Jinja2 conditionals within templates:
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"db_pool_size": 5,
|
|
17
17
|
"db_max_overflow": 10,
|
|
18
18
|
"db_pool_timeout": 30,
|
|
19
|
+
"orm_type": "sqlalchemy",
|
|
20
|
+
"use_sqlalchemy": true,
|
|
21
|
+
"use_sqlmodel": false,
|
|
19
22
|
"auth": "jwt",
|
|
20
23
|
"use_jwt": true,
|
|
21
24
|
"use_api_key": false,
|
|
@@ -58,6 +61,9 @@
|
|
|
58
61
|
"ai_framework": "pydantic_ai",
|
|
59
62
|
"use_pydantic_ai": true,
|
|
60
63
|
"use_langchain": false,
|
|
64
|
+
"use_langgraph": false,
|
|
65
|
+
"use_crewai": false,
|
|
66
|
+
"use_deepagents": false,
|
|
61
67
|
"llm_provider": "openai",
|
|
62
68
|
"use_openai": true,
|
|
63
69
|
"use_anthropic": false,
|
|
@@ -79,6 +85,10 @@
|
|
|
79
85
|
"reverse_proxy": "traefik_included",
|
|
80
86
|
"include_traefik_service": true,
|
|
81
87
|
"include_traefik_labels": true,
|
|
88
|
+
"use_traefik": true,
|
|
89
|
+
"include_nginx_service": false,
|
|
90
|
+
"include_nginx_config": false,
|
|
91
|
+
"use_nginx": false,
|
|
82
92
|
"ci_type": "github",
|
|
83
93
|
"use_github_actions": true,
|
|
84
94
|
"use_gitlab_ci": false,
|
|
@@ -16,10 +16,15 @@ use_database = "{{ cookiecutter.use_database }}" == "True"
|
|
|
16
16
|
use_postgresql = "{{ cookiecutter.use_postgresql }}" == "True"
|
|
17
17
|
use_sqlite = "{{ cookiecutter.use_sqlite }}" == "True"
|
|
18
18
|
use_mongodb = "{{ cookiecutter.use_mongodb }}" == "True"
|
|
19
|
+
use_sqlalchemy = "{{ cookiecutter.use_sqlalchemy }}" == "True"
|
|
20
|
+
use_sqlmodel = "{{ cookiecutter.use_sqlmodel }}" == "True"
|
|
19
21
|
include_example_crud = "{{ cookiecutter.include_example_crud }}" == "True"
|
|
20
22
|
enable_ai_agent = "{{ cookiecutter.enable_ai_agent }}" == "True"
|
|
21
23
|
use_pydantic_ai = "{{ cookiecutter.use_pydantic_ai }}" == "True"
|
|
22
24
|
use_langchain = "{{ cookiecutter.use_langchain }}" == "True"
|
|
25
|
+
use_langgraph = "{{ cookiecutter.use_langgraph }}" == "True"
|
|
26
|
+
use_crewai = "{{ cookiecutter.use_crewai }}" == "True"
|
|
27
|
+
use_deepagents = "{{ cookiecutter.use_deepagents }}" == "True"
|
|
23
28
|
enable_admin_panel = "{{ cookiecutter.enable_admin_panel }}" == "True"
|
|
24
29
|
enable_websockets = "{{ cookiecutter.enable_websockets }}" == "True"
|
|
25
30
|
enable_redis = "{{ cookiecutter.enable_redis }}" == "True"
|
|
@@ -30,9 +35,15 @@ enable_conversation_persistence = "{{ cookiecutter.enable_conversation_persisten
|
|
|
30
35
|
enable_webhooks = "{{ cookiecutter.enable_webhooks }}" == "True"
|
|
31
36
|
enable_oauth = "{{ cookiecutter.enable_oauth }}" == "True"
|
|
32
37
|
use_jwt = "{{ cookiecutter.use_jwt }}" == "True"
|
|
38
|
+
use_api_key = "{{ cookiecutter.use_api_key }}" == "True"
|
|
33
39
|
use_celery = "{{ cookiecutter.use_celery }}" == "True"
|
|
34
40
|
use_taskiq = "{{ cookiecutter.use_taskiq }}" == "True"
|
|
35
41
|
use_arq = "{{ cookiecutter.use_arq }}" == "True"
|
|
42
|
+
use_github_actions = "{{ cookiecutter.use_github_actions }}" == "True"
|
|
43
|
+
use_gitlab_ci = "{{ cookiecutter.use_gitlab_ci }}" == "True"
|
|
44
|
+
enable_kubernetes = "{{ cookiecutter.enable_kubernetes }}" == "True"
|
|
45
|
+
use_nginx = "{{ cookiecutter.use_nginx }}" == "True"
|
|
46
|
+
enable_logfire = "{{ cookiecutter.enable_logfire }}" == "True"
|
|
36
47
|
|
|
37
48
|
|
|
38
49
|
def remove_file(path: str) -> None:
|
|
@@ -42,6 +53,23 @@ def remove_file(path: str) -> None:
|
|
|
42
53
|
print(f" Removed: {os.path.relpath(path)}")
|
|
43
54
|
|
|
44
55
|
|
|
56
|
+
def is_stub_file(filepath: str) -> bool:
|
|
57
|
+
"""Check if file only contains a docstring stub with no real code."""
|
|
58
|
+
if not os.path.exists(filepath):
|
|
59
|
+
return False
|
|
60
|
+
with open(filepath) as f:
|
|
61
|
+
content = f.read().strip()
|
|
62
|
+
# Empty file
|
|
63
|
+
if not content:
|
|
64
|
+
return True
|
|
65
|
+
# File only has docstring (triple-quoted string)
|
|
66
|
+
if content.startswith('"""') and content.endswith('"""'):
|
|
67
|
+
# Check if there's only one docstring and no code
|
|
68
|
+
inner = content[3:-3].strip()
|
|
69
|
+
return '"""' not in inner and "def " not in content and "class " not in content
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
45
73
|
def remove_dir(path: str) -> None:
|
|
46
74
|
"""Remove a directory if it exists."""
|
|
47
75
|
if os.path.exists(path):
|
|
@@ -68,6 +96,12 @@ else:
|
|
|
68
96
|
remove_file(os.path.join(backend_app, "agents", "assistant.py"))
|
|
69
97
|
if not use_langchain:
|
|
70
98
|
remove_file(os.path.join(backend_app, "agents", "langchain_assistant.py"))
|
|
99
|
+
if not use_langgraph:
|
|
100
|
+
remove_file(os.path.join(backend_app, "agents", "langgraph_assistant.py"))
|
|
101
|
+
if not use_crewai:
|
|
102
|
+
remove_file(os.path.join(backend_app, "agents", "crewai_assistant.py"))
|
|
103
|
+
if not use_deepagents:
|
|
104
|
+
remove_file(os.path.join(backend_app, "agents", "deepagents_assistant.py"))
|
|
71
105
|
|
|
72
106
|
# --- Example CRUD files ---
|
|
73
107
|
if not include_example_crud or not use_database:
|
|
@@ -105,8 +139,8 @@ if not enable_session_management or not use_jwt:
|
|
|
105
139
|
if not enable_websockets:
|
|
106
140
|
remove_file(os.path.join(backend_app, "api", "routes", "v1", "ws.py"))
|
|
107
141
|
|
|
108
|
-
# --- Admin panel ---
|
|
109
|
-
if not enable_admin_panel or (not use_postgresql and not use_sqlite):
|
|
142
|
+
# --- Admin panel (requires SQLAlchemy, not SQLModel) ---
|
|
143
|
+
if not enable_admin_panel or (not use_postgresql and not use_sqlite) or not use_sqlalchemy:
|
|
110
144
|
remove_file(os.path.join(backend_app, "admin.py"))
|
|
111
145
|
|
|
112
146
|
# --- Redis/Cache files ---
|
|
@@ -125,6 +159,32 @@ if not enable_oauth:
|
|
|
125
159
|
remove_file(os.path.join(backend_app, "api", "routes", "v1", "oauth.py"))
|
|
126
160
|
remove_file(os.path.join(backend_app, "core", "oauth.py"))
|
|
127
161
|
|
|
162
|
+
# --- Security file (only when no auth at all) ---
|
|
163
|
+
if not use_jwt and not use_api_key:
|
|
164
|
+
remove_file(os.path.join(backend_app, "core", "security.py"))
|
|
165
|
+
|
|
166
|
+
# --- Auth/User files (when JWT is disabled) ---
|
|
167
|
+
if not use_jwt:
|
|
168
|
+
remove_file(os.path.join(backend_app, "api", "routes", "v1", "auth.py"))
|
|
169
|
+
remove_file(os.path.join(backend_app, "api", "routes", "v1", "users.py"))
|
|
170
|
+
remove_file(os.path.join(backend_app, "db", "models", "user.py"))
|
|
171
|
+
remove_file(os.path.join(backend_app, "repositories", "user.py"))
|
|
172
|
+
remove_file(os.path.join(backend_app, "services", "user.py"))
|
|
173
|
+
remove_file(os.path.join(backend_app, "schemas", "user.py"))
|
|
174
|
+
remove_file(os.path.join(backend_app, "schemas", "token.py"))
|
|
175
|
+
|
|
176
|
+
# --- Logfire setup file (when logfire is disabled) ---
|
|
177
|
+
if not enable_logfire:
|
|
178
|
+
remove_file(os.path.join(backend_app, "core", "logfire_setup.py"))
|
|
179
|
+
|
|
180
|
+
# --- Cleanup stub files (files with only docstring, no code) ---
|
|
181
|
+
core_dir = os.path.join(backend_app, "core")
|
|
182
|
+
for stub_candidate in ["security.py", "cache.py", "rate_limit.py", "oauth.py", "logfire_setup.py", "csrf.py"]:
|
|
183
|
+
filepath = os.path.join(core_dir, stub_candidate)
|
|
184
|
+
if is_stub_file(filepath):
|
|
185
|
+
remove_file(filepath)
|
|
186
|
+
print(f" Removed stub: {os.path.relpath(filepath)}")
|
|
187
|
+
|
|
128
188
|
# --- Worker/Background tasks ---
|
|
129
189
|
use_any_background_tasks = use_celery or use_taskiq or use_arq
|
|
130
190
|
if not use_any_background_tasks:
|
|
@@ -172,6 +232,31 @@ for subdir in ["clients", "agents", "worker", "worker/tasks"]:
|
|
|
172
232
|
|
|
173
233
|
print("File cleanup complete.")
|
|
174
234
|
|
|
235
|
+
# --- CI/CD files cleanup ---
|
|
236
|
+
if not use_github_actions:
|
|
237
|
+
github_dir = os.path.join(os.getcwd(), ".github")
|
|
238
|
+
if os.path.exists(github_dir):
|
|
239
|
+
shutil.rmtree(github_dir)
|
|
240
|
+
print("Removed .github/ directory (GitHub Actions not enabled)")
|
|
241
|
+
|
|
242
|
+
if not use_gitlab_ci:
|
|
243
|
+
gitlab_ci_file = os.path.join(os.getcwd(), ".gitlab-ci.yml")
|
|
244
|
+
if os.path.exists(gitlab_ci_file):
|
|
245
|
+
os.remove(gitlab_ci_file)
|
|
246
|
+
print("Removed .gitlab-ci.yml (GitLab CI not enabled)")
|
|
247
|
+
|
|
248
|
+
if not enable_kubernetes:
|
|
249
|
+
kubernetes_dir = os.path.join(os.getcwd(), "kubernetes")
|
|
250
|
+
if os.path.exists(kubernetes_dir):
|
|
251
|
+
shutil.rmtree(kubernetes_dir)
|
|
252
|
+
print("Removed kubernetes/ directory (Kubernetes not enabled)")
|
|
253
|
+
|
|
254
|
+
if not use_nginx:
|
|
255
|
+
nginx_dir = os.path.join(os.getcwd(), "nginx")
|
|
256
|
+
if os.path.exists(nginx_dir):
|
|
257
|
+
shutil.rmtree(nginx_dir)
|
|
258
|
+
print("Removed nginx/ directory (Nginx not enabled)")
|
|
259
|
+
|
|
175
260
|
# Remove frontend folder if not using frontend
|
|
176
261
|
if not use_frontend:
|
|
177
262
|
frontend_dir = os.path.join(os.getcwd(), "frontend")
|
|
@@ -17,6 +17,15 @@ DOMAIN=example.com
|
|
|
17
17
|
ACME_EMAIL=admin@example.com
|
|
18
18
|
{%- endif %}
|
|
19
19
|
|
|
20
|
+
{%- if cookiecutter.use_nginx %}
|
|
21
|
+
|
|
22
|
+
# === Domain Configuration ===
|
|
23
|
+
DOMAIN=example.com
|
|
24
|
+
# Note: Place SSL certificates in nginx/ssl/ directory:
|
|
25
|
+
# - nginx/ssl/cert.pem (certificate chain)
|
|
26
|
+
# - nginx/ssl/key.pem (private key)
|
|
27
|
+
{%- endif %}
|
|
28
|
+
|
|
20
29
|
{%- if cookiecutter.use_postgresql %}
|
|
21
30
|
|
|
22
31
|
# === PostgreSQL ===
|