fastapi-fullstack 0.1.7__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/METADATA +739 -0
- fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
- fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +442 -0
- fastapi_gen/config.py +356 -0
- fastapi_gen/generator.py +207 -0
- fastapi_gen/prompts.py +874 -0
- fastapi_gen/template/VARIABLES.md +276 -0
- fastapi_gen/template/cookiecutter.json +93 -0
- fastapi_gen/template/hooks/post_gen_project.py +355 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite -%}
|
|
2
|
+
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
|
|
3
|
+
"""
|
|
4
|
+
Seed database with sample data.
|
|
5
|
+
|
|
6
|
+
This command is useful for development and testing.
|
|
7
|
+
Uses random data generation - install faker for better data:
|
|
8
|
+
uv add faker --group dev
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import random
|
|
13
|
+
import string
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from sqlalchemy import delete, select
|
|
17
|
+
|
|
18
|
+
from app.commands import command, info, success, warning
|
|
19
|
+
|
|
20
|
+
# Try to import Faker for better data generation
|
|
21
|
+
try:
|
|
22
|
+
from faker import Faker
|
|
23
|
+
fake = Faker()
|
|
24
|
+
HAS_FAKER = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
HAS_FAKER = False
|
|
27
|
+
fake = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def random_email() -> str:
|
|
31
|
+
"""Generate a random email address."""
|
|
32
|
+
if HAS_FAKER:
|
|
33
|
+
return fake.email()
|
|
34
|
+
random_str = ''.join(random.choices(string.ascii_lowercase, k=8))
|
|
35
|
+
return f"{random_str}@example.com"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def random_name() -> str:
|
|
39
|
+
"""Generate a random full name."""
|
|
40
|
+
if HAS_FAKER:
|
|
41
|
+
return fake.name()
|
|
42
|
+
first_names = ["John", "Jane", "Bob", "Alice", "Charlie", "Diana", "Eve", "Frank"]
|
|
43
|
+
last_names = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis"]
|
|
44
|
+
return f"{random.choice(first_names)} {random.choice(last_names)}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def random_title() -> str:
|
|
48
|
+
"""Generate a random item title."""
|
|
49
|
+
if HAS_FAKER:
|
|
50
|
+
return fake.sentence(nb_words=4).rstrip('.')
|
|
51
|
+
adjectives = ["Amazing", "Great", "Awesome", "Fantastic", "Incredible", "Beautiful"]
|
|
52
|
+
nouns = ["Widget", "Gadget", "Thing", "Product", "Item", "Object"]
|
|
53
|
+
return f"{random.choice(adjectives)} {random.choice(nouns)}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def random_description() -> str:
|
|
57
|
+
"""Generate a random description."""
|
|
58
|
+
if HAS_FAKER:
|
|
59
|
+
return fake.paragraph(nb_sentences=3)
|
|
60
|
+
return "This is a sample description for development purposes."
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@command("seed", help="Seed database with sample data")
|
|
64
|
+
@click.option("--count", "-c", default=10, type=int, help="Number of records to create")
|
|
65
|
+
@click.option("--clear", is_flag=True, help="Clear existing data before seeding")
|
|
66
|
+
@click.option("--dry-run", is_flag=True, help="Show what would be created without making changes")
|
|
67
|
+
{%- if cookiecutter.use_jwt %}
|
|
68
|
+
@click.option("--users/--no-users", default=True, help="Seed users (default: True)")
|
|
69
|
+
{%- endif %}
|
|
70
|
+
{%- if cookiecutter.include_example_crud %}
|
|
71
|
+
@click.option("--items/--no-items", default=True, help="Seed items (default: True)")
|
|
72
|
+
{%- endif %}
|
|
73
|
+
def seed(
|
|
74
|
+
count: int,
|
|
75
|
+
clear: bool,
|
|
76
|
+
dry_run: bool,
|
|
77
|
+
{%- if cookiecutter.use_jwt %}
|
|
78
|
+
users: bool,
|
|
79
|
+
{%- endif %}
|
|
80
|
+
{%- if cookiecutter.include_example_crud %}
|
|
81
|
+
items: bool,
|
|
82
|
+
{%- endif %}
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Seed the database with sample data for development.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
project cmd seed --count 50
|
|
89
|
+
project cmd seed --clear --count 100
|
|
90
|
+
project cmd seed --dry-run
|
|
91
|
+
{%- if cookiecutter.use_jwt %}
|
|
92
|
+
project cmd seed --no-users --items # Only seed items
|
|
93
|
+
{%- endif %}
|
|
94
|
+
"""
|
|
95
|
+
if not HAS_FAKER:
|
|
96
|
+
warning("Faker not installed. Using basic random data. For better data: uv add faker --group dev")
|
|
97
|
+
|
|
98
|
+
if dry_run:
|
|
99
|
+
info(f"[DRY RUN] Would create {count} sample records per entity")
|
|
100
|
+
if clear:
|
|
101
|
+
info("[DRY RUN] Would clear existing data first")
|
|
102
|
+
{%- if cookiecutter.use_jwt %}
|
|
103
|
+
if users:
|
|
104
|
+
info("[DRY RUN] Would create users")
|
|
105
|
+
{%- endif %}
|
|
106
|
+
{%- if cookiecutter.include_example_crud %}
|
|
107
|
+
if items:
|
|
108
|
+
info("[DRY RUN] Would create items")
|
|
109
|
+
{%- endif %}
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
{%- if cookiecutter.use_postgresql %}
|
|
113
|
+
from app.db.session import async_session_maker
|
|
114
|
+
{%- if cookiecutter.use_jwt %}
|
|
115
|
+
from app.db.models.user import User
|
|
116
|
+
from app.core.security import get_password_hash
|
|
117
|
+
{%- endif %}
|
|
118
|
+
{%- if cookiecutter.include_example_crud %}
|
|
119
|
+
from app.db.models.item import Item
|
|
120
|
+
{%- endif %}
|
|
121
|
+
|
|
122
|
+
async def _seed():
|
|
123
|
+
async with async_session_maker() as session:
|
|
124
|
+
created_counts = {}
|
|
125
|
+
|
|
126
|
+
{%- if cookiecutter.use_jwt %}
|
|
127
|
+
# Seed users
|
|
128
|
+
if users:
|
|
129
|
+
if clear:
|
|
130
|
+
info("Clearing existing users (except superusers)...")
|
|
131
|
+
await session.execute(delete(User).where(User.is_superuser == False)) # noqa: E712
|
|
132
|
+
await session.commit()
|
|
133
|
+
|
|
134
|
+
# Check how many users already exist
|
|
135
|
+
result = await session.execute(select(User).limit(1))
|
|
136
|
+
existing = result.scalars().first()
|
|
137
|
+
|
|
138
|
+
if existing and not clear:
|
|
139
|
+
info("Users already exist. Use --clear to replace them.")
|
|
140
|
+
else:
|
|
141
|
+
info(f"Creating {count} sample users...")
|
|
142
|
+
for _ in range(count):
|
|
143
|
+
user = User(
|
|
144
|
+
email=random_email(),
|
|
145
|
+
hashed_password=get_password_hash("password123"),
|
|
146
|
+
full_name=random_name(),
|
|
147
|
+
is_active=True,
|
|
148
|
+
is_superuser=False,
|
|
149
|
+
role="user",
|
|
150
|
+
)
|
|
151
|
+
session.add(user)
|
|
152
|
+
await session.commit()
|
|
153
|
+
created_counts["users"] = count
|
|
154
|
+
{%- endif %}
|
|
155
|
+
|
|
156
|
+
{%- if cookiecutter.include_example_crud %}
|
|
157
|
+
# Seed items
|
|
158
|
+
if items:
|
|
159
|
+
if clear:
|
|
160
|
+
info("Clearing existing items...")
|
|
161
|
+
await session.execute(delete(Item))
|
|
162
|
+
await session.commit()
|
|
163
|
+
|
|
164
|
+
# Check how many items already exist
|
|
165
|
+
result = await session.execute(select(Item).limit(1))
|
|
166
|
+
existing = result.scalars().first()
|
|
167
|
+
|
|
168
|
+
if existing and not clear:
|
|
169
|
+
info("Items already exist. Use --clear to replace them.")
|
|
170
|
+
else:
|
|
171
|
+
info(f"Creating {count} sample items...")
|
|
172
|
+
for _ in range(count):
|
|
173
|
+
item = Item(
|
|
174
|
+
title=random_title(),
|
|
175
|
+
description=random_description(),
|
|
176
|
+
is_active=random.choice([True, True, True, False]), # 75% active
|
|
177
|
+
)
|
|
178
|
+
session.add(item)
|
|
179
|
+
await session.commit()
|
|
180
|
+
created_counts["items"] = count
|
|
181
|
+
{%- endif %}
|
|
182
|
+
|
|
183
|
+
if created_counts:
|
|
184
|
+
summary = ", ".join(f"{v} {k}" for k, v in created_counts.items())
|
|
185
|
+
success(f"Created: {summary}")
|
|
186
|
+
else:
|
|
187
|
+
info("No records created.")
|
|
188
|
+
|
|
189
|
+
asyncio.run(_seed())
|
|
190
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
191
|
+
from app.db.session import SessionLocal
|
|
192
|
+
{%- if cookiecutter.use_jwt %}
|
|
193
|
+
from app.db.models.user import User
|
|
194
|
+
from app.core.security import get_password_hash
|
|
195
|
+
{%- endif %}
|
|
196
|
+
{%- if cookiecutter.include_example_crud %}
|
|
197
|
+
from app.db.models.item import Item
|
|
198
|
+
{%- endif %}
|
|
199
|
+
|
|
200
|
+
with SessionLocal() as session:
|
|
201
|
+
created_counts = {}
|
|
202
|
+
|
|
203
|
+
{%- if cookiecutter.use_jwt %}
|
|
204
|
+
# Seed users
|
|
205
|
+
if users:
|
|
206
|
+
if clear:
|
|
207
|
+
info("Clearing existing users (except superusers)...")
|
|
208
|
+
session.execute(delete(User).where(User.is_superuser == False)) # noqa: E712
|
|
209
|
+
session.commit()
|
|
210
|
+
|
|
211
|
+
# Check how many users already exist
|
|
212
|
+
result = session.execute(select(User).limit(1))
|
|
213
|
+
existing = result.scalars().first()
|
|
214
|
+
|
|
215
|
+
if existing and not clear:
|
|
216
|
+
info("Users already exist. Use --clear to replace them.")
|
|
217
|
+
else:
|
|
218
|
+
info(f"Creating {count} sample users...")
|
|
219
|
+
for _ in range(count):
|
|
220
|
+
user = User(
|
|
221
|
+
email=random_email(),
|
|
222
|
+
hashed_password=get_password_hash("password123"),
|
|
223
|
+
full_name=random_name(),
|
|
224
|
+
is_active=True,
|
|
225
|
+
is_superuser=False,
|
|
226
|
+
role="user",
|
|
227
|
+
)
|
|
228
|
+
session.add(user)
|
|
229
|
+
session.commit()
|
|
230
|
+
created_counts["users"] = count
|
|
231
|
+
{%- endif %}
|
|
232
|
+
|
|
233
|
+
{%- if cookiecutter.include_example_crud %}
|
|
234
|
+
# Seed items
|
|
235
|
+
if items:
|
|
236
|
+
if clear:
|
|
237
|
+
info("Clearing existing items...")
|
|
238
|
+
session.execute(delete(Item))
|
|
239
|
+
session.commit()
|
|
240
|
+
|
|
241
|
+
# Check how many items already exist
|
|
242
|
+
result = session.execute(select(Item).limit(1))
|
|
243
|
+
existing = result.scalars().first()
|
|
244
|
+
|
|
245
|
+
if existing and not clear:
|
|
246
|
+
info("Items already exist. Use --clear to replace them.")
|
|
247
|
+
else:
|
|
248
|
+
info(f"Creating {count} sample items...")
|
|
249
|
+
for _ in range(count):
|
|
250
|
+
item = Item(
|
|
251
|
+
title=random_title(),
|
|
252
|
+
description=random_description(),
|
|
253
|
+
is_active=random.choice([True, True, True, False]), # 75% active
|
|
254
|
+
)
|
|
255
|
+
session.add(item)
|
|
256
|
+
session.commit()
|
|
257
|
+
created_counts["items"] = count
|
|
258
|
+
{%- endif %}
|
|
259
|
+
|
|
260
|
+
if created_counts:
|
|
261
|
+
summary = ", ".join(f"{v} {k}" for k, v in created_counts.items())
|
|
262
|
+
success(f"Created: {summary}")
|
|
263
|
+
else:
|
|
264
|
+
info("No records created.")
|
|
265
|
+
{%- endif %}
|
|
266
|
+
{%- endif %}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_caching %}
|
|
2
|
+
"""Caching configuration using fastapi-cache2."""
|
|
3
|
+
|
|
4
|
+
from fastapi_cache import FastAPICache
|
|
5
|
+
from fastapi_cache.backends.redis import RedisBackend
|
|
6
|
+
|
|
7
|
+
from app.clients.redis import RedisClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_cache(redis: RedisClient) -> None:
|
|
11
|
+
"""Initialize FastAPI cache with Redis backend.
|
|
12
|
+
|
|
13
|
+
Uses the shared Redis client from lifespan state.
|
|
14
|
+
"""
|
|
15
|
+
FastAPICache.init(RedisBackend(redis.raw), prefix="{{ cookiecutter.project_slug }}:cache:")
|
|
16
|
+
{%- else %}
|
|
17
|
+
"""Caching - not configured."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def setup_cache() -> None:
|
|
21
|
+
"""No-op when caching is disabled."""
|
|
22
|
+
pass
|
|
23
|
+
{%- endif %}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Application configuration using Pydantic BaseSettings."""
|
|
2
|
+
{% if cookiecutter.use_database -%}
|
|
3
|
+
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
|
|
4
|
+
{% endif %}
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
{% if cookiecutter.use_database -%}
|
|
9
|
+
from pydantic import computed_field, field_validator{% if cookiecutter.use_jwt or cookiecutter.use_api_key or cookiecutter.enable_cors %}, ValidationInfo{% endif %}
|
|
10
|
+
{% else -%}
|
|
11
|
+
from pydantic import field_validator{% if cookiecutter.use_jwt or cookiecutter.use_api_key or cookiecutter.enable_cors %}, ValidationInfo{% endif %}
|
|
12
|
+
{% endif -%}
|
|
13
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def find_env_file() -> Path | None:
|
|
17
|
+
"""Find .env file in current or parent directories."""
|
|
18
|
+
current = Path.cwd()
|
|
19
|
+
for path in [current, current.parent]:
|
|
20
|
+
env_file = path / ".env"
|
|
21
|
+
if env_file.exists():
|
|
22
|
+
return env_file
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Settings(BaseSettings):
|
|
27
|
+
"""Application settings."""
|
|
28
|
+
|
|
29
|
+
model_config = SettingsConfigDict(
|
|
30
|
+
env_file=find_env_file(),
|
|
31
|
+
env_ignore_empty=True,
|
|
32
|
+
extra="ignore",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# === Project ===
|
|
36
|
+
PROJECT_NAME: str = "{{ cookiecutter.project_name }}"
|
|
37
|
+
API_V1_STR: str = "/api/v1"
|
|
38
|
+
DEBUG: bool = False
|
|
39
|
+
ENVIRONMENT: Literal["development", "local", "staging", "production"] = "local"
|
|
40
|
+
|
|
41
|
+
{%- if cookiecutter.enable_logfire %}
|
|
42
|
+
|
|
43
|
+
# === Logfire ===
|
|
44
|
+
LOGFIRE_TOKEN: str | None = None
|
|
45
|
+
LOGFIRE_SERVICE_NAME: str = "{{ cookiecutter.project_slug }}"
|
|
46
|
+
LOGFIRE_ENVIRONMENT: str = "development"
|
|
47
|
+
{%- endif %}
|
|
48
|
+
|
|
49
|
+
{%- if cookiecutter.use_postgresql %}
|
|
50
|
+
|
|
51
|
+
# === Database (PostgreSQL async) ===
|
|
52
|
+
POSTGRES_HOST: str = "localhost"
|
|
53
|
+
POSTGRES_PORT: int = 5432
|
|
54
|
+
POSTGRES_USER: str = "postgres"
|
|
55
|
+
POSTGRES_PASSWORD: str = ""
|
|
56
|
+
POSTGRES_DB: str = "{{ cookiecutter.project_slug }}"
|
|
57
|
+
|
|
58
|
+
@computed_field # type: ignore[prop-decorator]
|
|
59
|
+
@property
|
|
60
|
+
def DATABASE_URL(self) -> str:
|
|
61
|
+
"""Build async PostgreSQL connection URL."""
|
|
62
|
+
return (
|
|
63
|
+
f"postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
|
|
64
|
+
f"@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@computed_field # type: ignore[prop-decorator]
|
|
68
|
+
@property
|
|
69
|
+
def DATABASE_URL_SYNC(self) -> str:
|
|
70
|
+
"""Build sync PostgreSQL connection URL (for Alembic)."""
|
|
71
|
+
return (
|
|
72
|
+
f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
|
|
73
|
+
f"@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Pool configuration
|
|
77
|
+
DB_POOL_SIZE: int = {{ cookiecutter.db_pool_size }}
|
|
78
|
+
DB_MAX_OVERFLOW: int = {{ cookiecutter.db_max_overflow }}
|
|
79
|
+
DB_POOL_TIMEOUT: int = {{ cookiecutter.db_pool_timeout }}
|
|
80
|
+
{%- endif %}
|
|
81
|
+
|
|
82
|
+
{%- if cookiecutter.use_mongodb %}
|
|
83
|
+
|
|
84
|
+
# === Database (MongoDB async) ===
|
|
85
|
+
MONGO_HOST: str = "localhost"
|
|
86
|
+
MONGO_PORT: int = 27017
|
|
87
|
+
MONGO_DB: str = "{{ cookiecutter.project_slug }}"
|
|
88
|
+
MONGO_USER: str | None = None
|
|
89
|
+
MONGO_PASSWORD: str | None = None
|
|
90
|
+
|
|
91
|
+
@computed_field # type: ignore[prop-decorator]
|
|
92
|
+
@property
|
|
93
|
+
def MONGO_URL(self) -> str:
|
|
94
|
+
"""Build MongoDB connection URL."""
|
|
95
|
+
if self.MONGO_USER and self.MONGO_PASSWORD:
|
|
96
|
+
return f"mongodb://{self.MONGO_USER}:{self.MONGO_PASSWORD}@{self.MONGO_HOST}:{self.MONGO_PORT}"
|
|
97
|
+
return f"mongodb://{self.MONGO_HOST}:{self.MONGO_PORT}"
|
|
98
|
+
{%- endif %}
|
|
99
|
+
|
|
100
|
+
{%- if cookiecutter.use_sqlite %}
|
|
101
|
+
|
|
102
|
+
# === Database (SQLite sync) ===
|
|
103
|
+
SQLITE_PATH: str = "./{{ cookiecutter.project_slug }}.db"
|
|
104
|
+
|
|
105
|
+
@computed_field # type: ignore[prop-decorator]
|
|
106
|
+
@property
|
|
107
|
+
def DATABASE_URL(self) -> str:
|
|
108
|
+
"""Build SQLite connection URL."""
|
|
109
|
+
return f"sqlite:///{self.SQLITE_PATH}"
|
|
110
|
+
{%- endif %}
|
|
111
|
+
|
|
112
|
+
{%- if cookiecutter.use_jwt %}
|
|
113
|
+
|
|
114
|
+
# === Auth (JWT) ===
|
|
115
|
+
SECRET_KEY: str = "change-me-in-production-use-openssl-rand-hex-32"
|
|
116
|
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 # 30 minutes
|
|
117
|
+
REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days
|
|
118
|
+
ALGORITHM: str = "HS256"
|
|
119
|
+
|
|
120
|
+
@field_validator("SECRET_KEY")
|
|
121
|
+
@classmethod
|
|
122
|
+
def validate_secret_key(cls, v: str, info: ValidationInfo) -> str:
|
|
123
|
+
"""Validate SECRET_KEY is secure in production."""
|
|
124
|
+
if len(v) < 32:
|
|
125
|
+
raise ValueError("SECRET_KEY must be at least 32 characters long")
|
|
126
|
+
# Get environment from values if available
|
|
127
|
+
env = info.data.get("ENVIRONMENT", "local") if info.data else "local"
|
|
128
|
+
if v == "change-me-in-production-use-openssl-rand-hex-32" and env == "production":
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"SECRET_KEY must be changed in production! "
|
|
131
|
+
"Generate a secure key with: openssl rand -hex 32"
|
|
132
|
+
)
|
|
133
|
+
return v
|
|
134
|
+
{%- endif %}
|
|
135
|
+
|
|
136
|
+
{%- if cookiecutter.enable_oauth_google %}
|
|
137
|
+
|
|
138
|
+
# === OAuth2 (Google) ===
|
|
139
|
+
GOOGLE_CLIENT_ID: str = ""
|
|
140
|
+
GOOGLE_CLIENT_SECRET: str = ""
|
|
141
|
+
GOOGLE_REDIRECT_URI: str = "http://localhost:{{ cookiecutter.backend_port }}/api/v1/oauth/google/callback"
|
|
142
|
+
{%- endif %}
|
|
143
|
+
|
|
144
|
+
{%- if cookiecutter.use_api_key %}
|
|
145
|
+
|
|
146
|
+
# === Auth (API Key) ===
|
|
147
|
+
API_KEY: str = "change-me-in-production"
|
|
148
|
+
API_KEY_HEADER: str = "X-API-Key"
|
|
149
|
+
|
|
150
|
+
@field_validator("API_KEY")
|
|
151
|
+
@classmethod
|
|
152
|
+
def validate_api_key(cls, v: str, info: ValidationInfo) -> str:
|
|
153
|
+
"""Validate API_KEY is set in production."""
|
|
154
|
+
env = info.data.get("ENVIRONMENT", "local") if info.data else "local"
|
|
155
|
+
if v == "change-me-in-production" and env == "production":
|
|
156
|
+
raise ValueError(
|
|
157
|
+
"API_KEY must be changed in production! "
|
|
158
|
+
"Generate a secure key with: openssl rand -hex 32"
|
|
159
|
+
)
|
|
160
|
+
return v
|
|
161
|
+
{%- endif %}
|
|
162
|
+
|
|
163
|
+
{%- if cookiecutter.enable_redis %}
|
|
164
|
+
|
|
165
|
+
# === Redis ===
|
|
166
|
+
REDIS_HOST: str = "localhost"
|
|
167
|
+
REDIS_PORT: int = 6379
|
|
168
|
+
REDIS_PASSWORD: str | None = None
|
|
169
|
+
REDIS_DB: int = 0
|
|
170
|
+
|
|
171
|
+
@computed_field # type: ignore[prop-decorator]
|
|
172
|
+
@property
|
|
173
|
+
def REDIS_URL(self) -> str:
|
|
174
|
+
"""Build Redis connection URL."""
|
|
175
|
+
if self.REDIS_PASSWORD:
|
|
176
|
+
return f"redis://:{self.REDIS_PASSWORD}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
|
177
|
+
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
|
178
|
+
{%- endif %}
|
|
179
|
+
|
|
180
|
+
{%- if cookiecutter.enable_rate_limiting %}
|
|
181
|
+
|
|
182
|
+
# === Rate Limiting ===
|
|
183
|
+
RATE_LIMIT_REQUESTS: int = {{ cookiecutter.rate_limit_requests }}
|
|
184
|
+
RATE_LIMIT_PERIOD: int = {{ cookiecutter.rate_limit_period }} # seconds
|
|
185
|
+
{%- endif %}
|
|
186
|
+
|
|
187
|
+
{%- if cookiecutter.use_celery %}
|
|
188
|
+
|
|
189
|
+
# === Celery ===
|
|
190
|
+
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
|
|
191
|
+
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/0"
|
|
192
|
+
{%- endif %}
|
|
193
|
+
|
|
194
|
+
{%- if cookiecutter.use_taskiq %}
|
|
195
|
+
|
|
196
|
+
# === Taskiq ===
|
|
197
|
+
TASKIQ_BROKER_URL: str = "redis://localhost:6379/1"
|
|
198
|
+
TASKIQ_RESULT_BACKEND: str = "redis://localhost:6379/1"
|
|
199
|
+
{%- endif %}
|
|
200
|
+
|
|
201
|
+
{%- if cookiecutter.enable_sentry %}
|
|
202
|
+
|
|
203
|
+
# === Sentry ===
|
|
204
|
+
SENTRY_DSN: str | None = None
|
|
205
|
+
{%- endif %}
|
|
206
|
+
|
|
207
|
+
{%- if cookiecutter.enable_file_storage %}
|
|
208
|
+
|
|
209
|
+
# === File Storage (S3/MinIO) ===
|
|
210
|
+
S3_ENDPOINT: str | None = None
|
|
211
|
+
S3_ACCESS_KEY: str = ""
|
|
212
|
+
S3_SECRET_KEY: str = ""
|
|
213
|
+
S3_BUCKET: str = "{{ cookiecutter.project_slug }}"
|
|
214
|
+
S3_REGION: str = "us-east-1"
|
|
215
|
+
{%- endif %}
|
|
216
|
+
|
|
217
|
+
{%- if cookiecutter.enable_ai_agent %}
|
|
218
|
+
|
|
219
|
+
# === AI Agent ({{ cookiecutter.ai_framework }}, {{ cookiecutter.llm_provider }}) ===
|
|
220
|
+
{%- if cookiecutter.use_openai %}
|
|
221
|
+
OPENAI_API_KEY: str = ""
|
|
222
|
+
AI_MODEL: str = "gpt-4o-mini"
|
|
223
|
+
{%- endif %}
|
|
224
|
+
{%- if cookiecutter.use_anthropic %}
|
|
225
|
+
ANTHROPIC_API_KEY: str = ""
|
|
226
|
+
AI_MODEL: str = "claude-sonnet-4-5-20241022"
|
|
227
|
+
{%- endif %}
|
|
228
|
+
{%- if cookiecutter.use_openrouter %}
|
|
229
|
+
OPENROUTER_API_KEY: str = ""
|
|
230
|
+
AI_MODEL: str = "anthropic/claude-3.5-sonnet"
|
|
231
|
+
{%- endif %}
|
|
232
|
+
AI_TEMPERATURE: float = 0.7
|
|
233
|
+
AI_FRAMEWORK: str = "{{ cookiecutter.ai_framework }}"
|
|
234
|
+
LLM_PROVIDER: str = "{{ cookiecutter.llm_provider }}"
|
|
235
|
+
{%- if cookiecutter.use_langchain %}
|
|
236
|
+
|
|
237
|
+
# === LangSmith (LangChain observability) ===
|
|
238
|
+
LANGCHAIN_TRACING_V2: bool = True
|
|
239
|
+
LANGCHAIN_API_KEY: str | None = None
|
|
240
|
+
LANGCHAIN_PROJECT: str = "{{ cookiecutter.project_slug }}"
|
|
241
|
+
LANGCHAIN_ENDPOINT: str = "https://api.smith.langchain.com"
|
|
242
|
+
{%- endif %}
|
|
243
|
+
{%- endif %}
|
|
244
|
+
|
|
245
|
+
{%- if cookiecutter.enable_cors %}
|
|
246
|
+
|
|
247
|
+
# === CORS ===
|
|
248
|
+
CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:8080"]
|
|
249
|
+
CORS_ALLOW_CREDENTIALS: bool = True
|
|
250
|
+
CORS_ALLOW_METHODS: list[str] = ["*"]
|
|
251
|
+
CORS_ALLOW_HEADERS: list[str] = ["*"]
|
|
252
|
+
|
|
253
|
+
@field_validator("CORS_ORIGINS")
|
|
254
|
+
@classmethod
|
|
255
|
+
def validate_cors_origins(cls, v: list[str], info: ValidationInfo) -> list[str]:
|
|
256
|
+
"""Warn if CORS_ORIGINS is too permissive in production."""
|
|
257
|
+
env = info.data.get("ENVIRONMENT", "local") if info.data else "local"
|
|
258
|
+
if "*" in v and env == "production":
|
|
259
|
+
raise ValueError(
|
|
260
|
+
"CORS_ORIGINS cannot contain '*' in production! "
|
|
261
|
+
"Specify explicit allowed origins."
|
|
262
|
+
)
|
|
263
|
+
return v
|
|
264
|
+
{%- endif %}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
settings = Settings()
|