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,271 @@
|
|
|
1
|
+
"""Input sanitization utilities.
|
|
2
|
+
|
|
3
|
+
This module provides security-focused input sanitization functions:
|
|
4
|
+
- HTML sanitization to prevent XSS attacks
|
|
5
|
+
- Path traversal prevention for file operations
|
|
6
|
+
- Common input cleaning utilities
|
|
7
|
+
|
|
8
|
+
Note: SQL injection is prevented by using SQLAlchemy ORM with parameterized queries.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import html
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import unicodedata
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TypeVar
|
|
17
|
+
|
|
18
|
+
# Default allowed HTML tags for rich text content
|
|
19
|
+
DEFAULT_ALLOWED_TAGS = frozenset({
|
|
20
|
+
"a", "abbr", "acronym", "b", "blockquote", "br", "code",
|
|
21
|
+
"em", "i", "li", "ol", "p", "pre", "strong", "ul",
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
# Default allowed HTML attributes
|
|
25
|
+
DEFAULT_ALLOWED_ATTRIBUTES = {
|
|
26
|
+
"a": frozenset({"href", "title", "rel"}),
|
|
27
|
+
"abbr": frozenset({"title"}),
|
|
28
|
+
"acronym": frozenset({"title"}),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sanitize_html(
|
|
33
|
+
content: str,
|
|
34
|
+
allowed_tags: frozenset[str] | None = None,
|
|
35
|
+
strip: bool = True,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""Sanitize HTML content to prevent XSS attacks.
|
|
38
|
+
|
|
39
|
+
This is a simple implementation that escapes all HTML.
|
|
40
|
+
For rich text support, consider using the `bleach` library.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
content: The HTML content to sanitize.
|
|
44
|
+
allowed_tags: Not used in simple mode (for bleach compatibility).
|
|
45
|
+
strip: Not used in simple mode (for bleach compatibility).
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Escaped HTML-safe string.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> sanitize_html("<script>alert('xss')</script>")
|
|
52
|
+
"<script>alert('xss')</script>"
|
|
53
|
+
"""
|
|
54
|
+
if not content:
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
return html.escape(content)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def sanitize_filename(filename: str, allow_unicode: bool = False) -> str:
|
|
61
|
+
"""Sanitize a filename to prevent path traversal and unsafe characters.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
filename: The filename to sanitize.
|
|
65
|
+
allow_unicode: Whether to allow unicode characters.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A safe filename string.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> sanitize_filename("../../../etc/passwd")
|
|
72
|
+
"etc_passwd"
|
|
73
|
+
>>> sanitize_filename("hello world.txt")
|
|
74
|
+
"hello_world.txt"
|
|
75
|
+
"""
|
|
76
|
+
if not filename:
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
# Normalize unicode
|
|
80
|
+
if allow_unicode:
|
|
81
|
+
filename = unicodedata.normalize("NFKC", filename)
|
|
82
|
+
else:
|
|
83
|
+
filename = (
|
|
84
|
+
unicodedata.normalize("NFKD", filename)
|
|
85
|
+
.encode("ascii", "ignore")
|
|
86
|
+
.decode("ascii")
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Get just the filename (remove any path components)
|
|
90
|
+
filename = os.path.basename(filename)
|
|
91
|
+
|
|
92
|
+
# Remove null bytes
|
|
93
|
+
filename = filename.replace("\x00", "")
|
|
94
|
+
|
|
95
|
+
# Replace path separators and special characters
|
|
96
|
+
filename = re.sub(r"[/\\:*?\"<>|]", "_", filename)
|
|
97
|
+
|
|
98
|
+
# Replace multiple underscores/spaces with single underscore
|
|
99
|
+
filename = re.sub(r"[\s_]+", "_", filename)
|
|
100
|
+
|
|
101
|
+
# Remove leading/trailing underscores and dots
|
|
102
|
+
filename = filename.strip("._")
|
|
103
|
+
|
|
104
|
+
# Ensure we have a valid filename
|
|
105
|
+
if not filename:
|
|
106
|
+
return "unnamed"
|
|
107
|
+
|
|
108
|
+
return filename
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_safe_path(
|
|
112
|
+
base_dir: Path | str,
|
|
113
|
+
user_path: str,
|
|
114
|
+
) -> Path:
|
|
115
|
+
"""Validate that a user-provided path is within the allowed base directory.
|
|
116
|
+
|
|
117
|
+
Prevents path traversal attacks by ensuring the resolved path
|
|
118
|
+
is within the expected directory.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
base_dir: The base directory that all paths must be within.
|
|
122
|
+
user_path: The user-provided path to validate.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The resolved, safe path.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError: If the path would escape the base directory.
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> validate_safe_path("/uploads", "../../../etc/passwd")
|
|
132
|
+
Raises ValueError
|
|
133
|
+
>>> validate_safe_path("/uploads", "images/photo.jpg")
|
|
134
|
+
Path("/uploads/images/photo.jpg")
|
|
135
|
+
"""
|
|
136
|
+
base_path = Path(base_dir).resolve()
|
|
137
|
+
user_path_sanitized = sanitize_filename(user_path.lstrip("/\\"))
|
|
138
|
+
|
|
139
|
+
# Resolve the full path
|
|
140
|
+
full_path = (base_path / user_path_sanitized).resolve()
|
|
141
|
+
|
|
142
|
+
# Check if the resolved path is within the base directory
|
|
143
|
+
try:
|
|
144
|
+
full_path.relative_to(base_path)
|
|
145
|
+
except ValueError as err:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Path traversal detected: {user_path!r} would escape {base_dir!r}"
|
|
148
|
+
) from err
|
|
149
|
+
|
|
150
|
+
return full_path
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def sanitize_string(
|
|
154
|
+
value: str,
|
|
155
|
+
max_length: int | None = None,
|
|
156
|
+
allow_newlines: bool = True,
|
|
157
|
+
strip_whitespace: bool = True,
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Sanitize a string input with various options.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
value: The string to sanitize.
|
|
163
|
+
max_length: Maximum allowed length (truncates if exceeded).
|
|
164
|
+
allow_newlines: Whether to preserve newlines.
|
|
165
|
+
strip_whitespace: Whether to strip leading/trailing whitespace.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Sanitized string.
|
|
169
|
+
"""
|
|
170
|
+
if not value:
|
|
171
|
+
return ""
|
|
172
|
+
|
|
173
|
+
# Strip null bytes and other control characters (except newlines if allowed)
|
|
174
|
+
if allow_newlines:
|
|
175
|
+
value = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]", "", value)
|
|
176
|
+
else:
|
|
177
|
+
value = re.sub(r"[\x00-\x1f\x7f]", "", value)
|
|
178
|
+
|
|
179
|
+
# Strip whitespace if requested
|
|
180
|
+
if strip_whitespace:
|
|
181
|
+
value = value.strip()
|
|
182
|
+
|
|
183
|
+
# Truncate if needed
|
|
184
|
+
if max_length is not None and len(value) > max_length:
|
|
185
|
+
value = value[:max_length]
|
|
186
|
+
|
|
187
|
+
return value
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def sanitize_email(email: str) -> str:
|
|
191
|
+
"""Basic email sanitization.
|
|
192
|
+
|
|
193
|
+
Note: For proper email validation, use Pydantic's EmailStr type.
|
|
194
|
+
This function only performs basic cleaning.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
email: The email address to sanitize.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Lowercased, stripped email.
|
|
201
|
+
"""
|
|
202
|
+
if not email:
|
|
203
|
+
return ""
|
|
204
|
+
|
|
205
|
+
return email.strip().lower()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
T = TypeVar("T", int, float)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def sanitize_numeric(
|
|
212
|
+
value: str | int | float,
|
|
213
|
+
value_type: type[T],
|
|
214
|
+
min_value: T | None = None,
|
|
215
|
+
max_value: T | None = None,
|
|
216
|
+
default: T | None = None,
|
|
217
|
+
) -> T | None:
|
|
218
|
+
"""Sanitize and validate a numeric value.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
value: The value to sanitize (can be string or numeric).
|
|
222
|
+
value_type: The expected type (int or float).
|
|
223
|
+
min_value: Minimum allowed value.
|
|
224
|
+
max_value: Maximum allowed value.
|
|
225
|
+
default: Default value if conversion fails.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
The sanitized numeric value, or default if invalid.
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
>>> sanitize_numeric("100", int, min_value=0, max_value=1000)
|
|
232
|
+
100
|
|
233
|
+
>>> sanitize_numeric("abc", int, default=0)
|
|
234
|
+
0
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
result = value_type(value)
|
|
238
|
+
|
|
239
|
+
if min_value is not None and result < min_value:
|
|
240
|
+
result = min_value
|
|
241
|
+
if max_value is not None and result > max_value:
|
|
242
|
+
result = max_value
|
|
243
|
+
|
|
244
|
+
return result
|
|
245
|
+
except (ValueError, TypeError):
|
|
246
|
+
return default
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def escape_sql_like(pattern: str, escape_char: str = "\\") -> str:
|
|
250
|
+
"""Escape special characters in a LIKE pattern.
|
|
251
|
+
|
|
252
|
+
Use this when building LIKE queries with user input.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
pattern: The pattern to escape.
|
|
256
|
+
escape_char: The escape character to use.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Escaped pattern safe for use in LIKE queries.
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
>>> escape_sql_like("100%")
|
|
263
|
+
"100\\%"
|
|
264
|
+
>>> escape_sql_like("under_score")
|
|
265
|
+
"under\\_score"
|
|
266
|
+
"""
|
|
267
|
+
# Escape the escape character first, then special chars
|
|
268
|
+
pattern = pattern.replace(escape_char, escape_char + escape_char)
|
|
269
|
+
pattern = pattern.replace("%", escape_char + "%")
|
|
270
|
+
pattern = pattern.replace("_", escape_char + "_")
|
|
271
|
+
return pattern
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{%- if cookiecutter.use_jwt %}
|
|
2
|
+
"""Security utilities for JWT authentication."""
|
|
3
|
+
|
|
4
|
+
from datetime import UTC, datetime, timedelta
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import bcrypt
|
|
8
|
+
import jwt
|
|
9
|
+
|
|
10
|
+
from app.core.config import settings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_access_token(
|
|
14
|
+
subject: str | Any,
|
|
15
|
+
expires_delta: timedelta | None = None,
|
|
16
|
+
) -> str:
|
|
17
|
+
"""Create a JWT access token."""
|
|
18
|
+
if expires_delta:
|
|
19
|
+
expire = datetime.now(UTC) + expires_delta
|
|
20
|
+
else:
|
|
21
|
+
expire = datetime.now(UTC) + timedelta(
|
|
22
|
+
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
to_encode = {"exp": expire, "sub": str(subject), "type": "access"}
|
|
26
|
+
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_refresh_token(
|
|
30
|
+
subject: str | Any,
|
|
31
|
+
expires_delta: timedelta | None = None,
|
|
32
|
+
) -> str:
|
|
33
|
+
"""Create a JWT refresh token."""
|
|
34
|
+
if expires_delta:
|
|
35
|
+
expire = datetime.now(UTC) + expires_delta
|
|
36
|
+
else:
|
|
37
|
+
expire = datetime.now(UTC) + timedelta(
|
|
38
|
+
minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
to_encode = {"exp": expire, "sub": str(subject), "type": "refresh"}
|
|
42
|
+
return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def verify_token(token: str) -> dict[str, Any] | None:
|
|
46
|
+
"""Verify a JWT token and return payload."""
|
|
47
|
+
try:
|
|
48
|
+
payload = jwt.decode(
|
|
49
|
+
token,
|
|
50
|
+
settings.SECRET_KEY,
|
|
51
|
+
algorithms=[settings.ALGORITHM],
|
|
52
|
+
)
|
|
53
|
+
return payload
|
|
54
|
+
except jwt.PyJWTError:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
59
|
+
"""Verify a password against a hash."""
|
|
60
|
+
return bcrypt.checkpw(
|
|
61
|
+
plain_password.encode("utf-8"),
|
|
62
|
+
hashed_password.encode("utf-8"),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_password_hash(password: str) -> str:
|
|
67
|
+
"""Hash a password."""
|
|
68
|
+
return bcrypt.hashpw(
|
|
69
|
+
password.encode("utf-8"),
|
|
70
|
+
bcrypt.gensalt(),
|
|
71
|
+
).decode("utf-8")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
{%- elif cookiecutter.use_api_key %}
|
|
75
|
+
"""Security utilities for API Key authentication."""
|
|
76
|
+
|
|
77
|
+
from fastapi import HTTPException, Security, status
|
|
78
|
+
from fastapi.security import APIKeyHeader
|
|
79
|
+
|
|
80
|
+
from app.core.config import settings
|
|
81
|
+
|
|
82
|
+
api_key_header = APIKeyHeader(name=settings.API_KEY_HEADER, auto_error=False)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def verify_api_key(api_key: str = Security(api_key_header)) -> str:
|
|
86
|
+
"""Verify API key from header."""
|
|
87
|
+
if api_key is None:
|
|
88
|
+
raise HTTPException(
|
|
89
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
90
|
+
detail="API Key header missing",
|
|
91
|
+
)
|
|
92
|
+
if api_key != settings.API_KEY:
|
|
93
|
+
raise HTTPException(
|
|
94
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
95
|
+
detail="Invalid API Key",
|
|
96
|
+
)
|
|
97
|
+
return api_key
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
{%- else %}
|
|
101
|
+
"""Security - not configured."""
|
|
102
|
+
{%- endif %}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
|
|
2
|
+
"""SQLAlchemy base model."""
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import DateTime, MetaData, func
|
|
7
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
8
|
+
|
|
9
|
+
# Naming convention for database constraints and indexes
|
|
10
|
+
# This ensures consistent naming across all migrations
|
|
11
|
+
NAMING_CONVENTION = {
|
|
12
|
+
"ix": "%(column_0_label)s_idx",
|
|
13
|
+
"uq": "%(table_name)s_%(column_0_name)s_key",
|
|
14
|
+
"ck": "%(table_name)s_%(constraint_name)s_check",
|
|
15
|
+
"fk": "%(table_name)s_%(column_0_name)s_fkey",
|
|
16
|
+
"pk": "%(table_name)s_pkey",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Base(DeclarativeBase):
|
|
21
|
+
"""Base class for all SQLAlchemy models."""
|
|
22
|
+
|
|
23
|
+
metadata = MetaData(naming_convention=NAMING_CONVENTION)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TimestampMixin:
|
|
27
|
+
"""Mixin for created_at and updated_at timestamps."""
|
|
28
|
+
|
|
29
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
30
|
+
DateTime(timezone=True),
|
|
31
|
+
server_default=func.now(),
|
|
32
|
+
nullable=False,
|
|
33
|
+
)
|
|
34
|
+
updated_at: Mapped[datetime | None] = mapped_column(
|
|
35
|
+
DateTime(timezone=True),
|
|
36
|
+
onupdate=func.now(),
|
|
37
|
+
nullable=True,
|
|
38
|
+
)
|
|
39
|
+
{%- else %}
|
|
40
|
+
"""Database base - not using SQLAlchemy."""
|
|
41
|
+
{%- endif %}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Database models."""
|
|
2
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
|
|
3
|
+
# ruff: noqa: I001, RUF022 - Imports structured for Jinja2 template conditionals
|
|
4
|
+
{%- endif %}
|
|
5
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
|
|
6
|
+
{%- set models = [] %}
|
|
7
|
+
{%- if cookiecutter.use_jwt %}
|
|
8
|
+
{%- set _ = models.append("User") %}
|
|
9
|
+
from app.db.models.user import User
|
|
10
|
+
{%- endif %}
|
|
11
|
+
{%- if cookiecutter.enable_session_management and cookiecutter.use_jwt %}
|
|
12
|
+
{%- set _ = models.append("Session") %}
|
|
13
|
+
from app.db.models.session import Session
|
|
14
|
+
{%- endif %}
|
|
15
|
+
{%- if cookiecutter.include_example_crud %}
|
|
16
|
+
{%- set _ = models.append("Item") %}
|
|
17
|
+
from app.db.models.item import Item
|
|
18
|
+
{%- endif %}
|
|
19
|
+
{%- if cookiecutter.enable_conversation_persistence %}
|
|
20
|
+
{%- set _ = models.extend(["Conversation", "Message", "ToolCall"]) %}
|
|
21
|
+
from app.db.models.conversation import Conversation, Message, ToolCall
|
|
22
|
+
{%- endif %}
|
|
23
|
+
{%- if cookiecutter.enable_webhooks %}
|
|
24
|
+
{%- set _ = models.extend(["Webhook", "WebhookDelivery"]) %}
|
|
25
|
+
from app.db.models.webhook import Webhook, WebhookDelivery
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{%- if models %}
|
|
28
|
+
|
|
29
|
+
__all__ = {{ models }}
|
|
30
|
+
{%- endif %}
|
|
31
|
+
{%- endif %}
|