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,319 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_postgresql %}
|
|
2
|
+
"""Conversation and message models for AI chat persistence."""
|
|
3
|
+
|
|
4
|
+
import uuid
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
|
|
9
|
+
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
10
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
11
|
+
|
|
12
|
+
from app.db.base import Base, TimestampMixin
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Conversation(Base, TimestampMixin):
|
|
16
|
+
"""Conversation model - groups messages in a chat session.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
id: Unique conversation identifier
|
|
20
|
+
user_id: Optional user who owns this conversation (if auth enabled)
|
|
21
|
+
title: Auto-generated or user-defined title
|
|
22
|
+
is_archived: Whether the conversation is archived
|
|
23
|
+
messages: List of messages in this conversation
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__tablename__ = "conversations"
|
|
27
|
+
|
|
28
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
29
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
30
|
+
)
|
|
31
|
+
{%- if cookiecutter.use_jwt %}
|
|
32
|
+
user_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
33
|
+
UUID(as_uuid=True),
|
|
34
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
35
|
+
nullable=True,
|
|
36
|
+
index=True,
|
|
37
|
+
)
|
|
38
|
+
{%- endif %}
|
|
39
|
+
title: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
40
|
+
is_archived: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
41
|
+
|
|
42
|
+
# Relationships
|
|
43
|
+
messages: Mapped[list["Message"]] = relationship(
|
|
44
|
+
"Message",
|
|
45
|
+
back_populates="conversation",
|
|
46
|
+
cascade="all, delete-orphan",
|
|
47
|
+
order_by="Message.created_at",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __repr__(self) -> str:
|
|
51
|
+
return f"<Conversation(id={self.id}, title={self.title})>"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Message(Base, TimestampMixin):
|
|
55
|
+
"""Message model - individual message in a conversation.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
id: Unique message identifier
|
|
59
|
+
conversation_id: The conversation this message belongs to
|
|
60
|
+
role: Message role (user, assistant, system)
|
|
61
|
+
content: Message text content
|
|
62
|
+
model_name: AI model used (for assistant messages)
|
|
63
|
+
tokens_used: Token count for this message
|
|
64
|
+
tool_calls: List of tool calls made in this message
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
__tablename__ = "messages"
|
|
68
|
+
|
|
69
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
70
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
71
|
+
)
|
|
72
|
+
conversation_id: Mapped[uuid.UUID] = mapped_column(
|
|
73
|
+
UUID(as_uuid=True),
|
|
74
|
+
ForeignKey("conversations.id", ondelete="CASCADE"),
|
|
75
|
+
nullable=False,
|
|
76
|
+
index=True,
|
|
77
|
+
)
|
|
78
|
+
role: Mapped[str] = mapped_column(
|
|
79
|
+
String(20), nullable=False
|
|
80
|
+
) # user, assistant, system
|
|
81
|
+
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
82
|
+
model_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
83
|
+
tokens_used: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
84
|
+
|
|
85
|
+
# Relationships
|
|
86
|
+
conversation: Mapped["Conversation"] = relationship(
|
|
87
|
+
"Conversation", back_populates="messages"
|
|
88
|
+
)
|
|
89
|
+
tool_calls: Mapped[list["ToolCall"]] = relationship(
|
|
90
|
+
"ToolCall",
|
|
91
|
+
back_populates="message",
|
|
92
|
+
cascade="all, delete-orphan",
|
|
93
|
+
order_by="ToolCall.started_at",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def __repr__(self) -> str:
|
|
97
|
+
return f"<Message(id={self.id}, role={self.role})>"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ToolCall(Base):
|
|
101
|
+
"""ToolCall model - record of a tool invocation.
|
|
102
|
+
|
|
103
|
+
Attributes:
|
|
104
|
+
id: Unique tool call identifier
|
|
105
|
+
message_id: The assistant message that triggered this call
|
|
106
|
+
tool_call_id: External ID from PydanticAI
|
|
107
|
+
tool_name: Name of the tool that was called
|
|
108
|
+
args: JSON arguments passed to the tool
|
|
109
|
+
result: Result returned by the tool
|
|
110
|
+
status: Current status (pending, running, completed, failed)
|
|
111
|
+
started_at: When the tool call started
|
|
112
|
+
completed_at: When the tool call completed
|
|
113
|
+
duration_ms: Execution time in milliseconds
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
__tablename__ = "tool_calls"
|
|
117
|
+
|
|
118
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
119
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
120
|
+
)
|
|
121
|
+
message_id: Mapped[uuid.UUID] = mapped_column(
|
|
122
|
+
UUID(as_uuid=True),
|
|
123
|
+
ForeignKey("messages.id", ondelete="CASCADE"),
|
|
124
|
+
nullable=False,
|
|
125
|
+
index=True,
|
|
126
|
+
)
|
|
127
|
+
tool_call_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
128
|
+
tool_name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
129
|
+
args: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)
|
|
130
|
+
result: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
131
|
+
status: Mapped[str] = mapped_column(
|
|
132
|
+
String(20), nullable=False, default="pending"
|
|
133
|
+
) # pending, running, completed, failed
|
|
134
|
+
started_at: Mapped[datetime] = mapped_column(
|
|
135
|
+
DateTime(timezone=True), nullable=False
|
|
136
|
+
)
|
|
137
|
+
completed_at: Mapped[datetime | None] = mapped_column(
|
|
138
|
+
DateTime(timezone=True), nullable=True
|
|
139
|
+
)
|
|
140
|
+
duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
141
|
+
|
|
142
|
+
# Relationships
|
|
143
|
+
message: Mapped["Message"] = relationship("Message", back_populates="tool_calls")
|
|
144
|
+
|
|
145
|
+
def __repr__(self) -> str:
|
|
146
|
+
return f"<ToolCall(id={self.id}, tool_name={self.tool_name}, status={self.status})>"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
{%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_sqlite %}
|
|
150
|
+
"""Conversation and message models for AI chat persistence."""
|
|
151
|
+
|
|
152
|
+
import uuid
|
|
153
|
+
from datetime import datetime
|
|
154
|
+
|
|
155
|
+
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
|
|
156
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
157
|
+
|
|
158
|
+
from app.db.base import Base, TimestampMixin
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Conversation(Base, TimestampMixin):
|
|
162
|
+
"""Conversation model - groups messages in a chat session."""
|
|
163
|
+
|
|
164
|
+
__tablename__ = "conversations"
|
|
165
|
+
|
|
166
|
+
id: Mapped[str] = mapped_column(
|
|
167
|
+
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
168
|
+
)
|
|
169
|
+
{%- if cookiecutter.use_jwt %}
|
|
170
|
+
user_id: Mapped[str | None] = mapped_column(
|
|
171
|
+
String(36),
|
|
172
|
+
ForeignKey("users.id", ondelete="CASCADE"),
|
|
173
|
+
nullable=True,
|
|
174
|
+
index=True,
|
|
175
|
+
)
|
|
176
|
+
{%- endif %}
|
|
177
|
+
title: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
178
|
+
is_archived: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
179
|
+
|
|
180
|
+
# Relationships
|
|
181
|
+
messages: Mapped[list["Message"]] = relationship(
|
|
182
|
+
"Message",
|
|
183
|
+
back_populates="conversation",
|
|
184
|
+
cascade="all, delete-orphan",
|
|
185
|
+
order_by="Message.created_at",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def __repr__(self) -> str:
|
|
189
|
+
return f"<Conversation(id={self.id}, title={self.title})>"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Message(Base, TimestampMixin):
|
|
193
|
+
"""Message model - individual message in a conversation."""
|
|
194
|
+
|
|
195
|
+
__tablename__ = "messages"
|
|
196
|
+
|
|
197
|
+
id: Mapped[str] = mapped_column(
|
|
198
|
+
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
199
|
+
)
|
|
200
|
+
conversation_id: Mapped[str] = mapped_column(
|
|
201
|
+
String(36),
|
|
202
|
+
ForeignKey("conversations.id", ondelete="CASCADE"),
|
|
203
|
+
nullable=False,
|
|
204
|
+
index=True,
|
|
205
|
+
)
|
|
206
|
+
role: Mapped[str] = mapped_column(String(20), nullable=False)
|
|
207
|
+
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
208
|
+
model_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
209
|
+
tokens_used: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
210
|
+
|
|
211
|
+
# Relationships
|
|
212
|
+
conversation: Mapped["Conversation"] = relationship(
|
|
213
|
+
"Conversation", back_populates="messages"
|
|
214
|
+
)
|
|
215
|
+
tool_calls: Mapped[list["ToolCall"]] = relationship(
|
|
216
|
+
"ToolCall",
|
|
217
|
+
back_populates="message",
|
|
218
|
+
cascade="all, delete-orphan",
|
|
219
|
+
order_by="ToolCall.started_at",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def __repr__(self) -> str:
|
|
223
|
+
return f"<Message(id={self.id}, role={self.role})>"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ToolCall(Base):
|
|
227
|
+
"""ToolCall model - record of a tool invocation."""
|
|
228
|
+
|
|
229
|
+
__tablename__ = "tool_calls"
|
|
230
|
+
|
|
231
|
+
id: Mapped[str] = mapped_column(
|
|
232
|
+
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
233
|
+
)
|
|
234
|
+
message_id: Mapped[str] = mapped_column(
|
|
235
|
+
String(36),
|
|
236
|
+
ForeignKey("messages.id", ondelete="CASCADE"),
|
|
237
|
+
nullable=False,
|
|
238
|
+
index=True,
|
|
239
|
+
)
|
|
240
|
+
tool_call_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
241
|
+
tool_name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
242
|
+
args: Mapped[str] = mapped_column(Text, nullable=False, default="{}") # JSON as string
|
|
243
|
+
result: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
244
|
+
status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
|
|
245
|
+
started_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
246
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
247
|
+
duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
248
|
+
|
|
249
|
+
# Relationships
|
|
250
|
+
message: Mapped["Message"] = relationship("Message", back_populates="tool_calls")
|
|
251
|
+
|
|
252
|
+
def __repr__(self) -> str:
|
|
253
|
+
return f"<ToolCall(id={self.id}, tool_name={self.tool_name})>"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
{%- elif cookiecutter.enable_conversation_persistence and cookiecutter.use_mongodb %}
|
|
257
|
+
"""Conversation and message models for AI chat persistence (MongoDB)."""
|
|
258
|
+
|
|
259
|
+
from datetime import UTC, datetime
|
|
260
|
+
from typing import Literal, Optional
|
|
261
|
+
|
|
262
|
+
from beanie import Document, Link
|
|
263
|
+
from pydantic import Field
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ToolCall(Document):
|
|
267
|
+
"""ToolCall document model - record of a tool invocation."""
|
|
268
|
+
|
|
269
|
+
message_id: str
|
|
270
|
+
tool_call_id: str
|
|
271
|
+
tool_name: str
|
|
272
|
+
args: dict = Field(default_factory=dict)
|
|
273
|
+
result: Optional[str] = None
|
|
274
|
+
status: Literal["pending", "running", "completed", "failed"] = "pending"
|
|
275
|
+
started_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
276
|
+
completed_at: Optional[datetime] = None
|
|
277
|
+
duration_ms: Optional[int] = None
|
|
278
|
+
|
|
279
|
+
class Settings:
|
|
280
|
+
name = "tool_calls"
|
|
281
|
+
indexes = ["message_id"]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class Message(Document):
|
|
285
|
+
"""Message document model - individual message in a conversation."""
|
|
286
|
+
|
|
287
|
+
conversation_id: str
|
|
288
|
+
role: Literal["user", "assistant", "system"]
|
|
289
|
+
content: str
|
|
290
|
+
model_name: Optional[str] = None
|
|
291
|
+
tokens_used: Optional[int] = None
|
|
292
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
293
|
+
|
|
294
|
+
class Settings:
|
|
295
|
+
name = "messages"
|
|
296
|
+
indexes = ["conversation_id"]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class Conversation(Document):
|
|
300
|
+
"""Conversation document model - groups messages in a chat session."""
|
|
301
|
+
|
|
302
|
+
{%- if cookiecutter.use_jwt %}
|
|
303
|
+
user_id: Optional[str] = None
|
|
304
|
+
{%- endif %}
|
|
305
|
+
title: Optional[str] = None
|
|
306
|
+
is_archived: bool = False
|
|
307
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
308
|
+
updated_at: Optional[datetime] = None
|
|
309
|
+
|
|
310
|
+
class Settings:
|
|
311
|
+
name = "conversations"
|
|
312
|
+
{%- if cookiecutter.use_jwt %}
|
|
313
|
+
indexes = ["user_id"]
|
|
314
|
+
{%- endif %}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
{%- else %}
|
|
318
|
+
"""Conversation models - not configured."""
|
|
319
|
+
{%- endif %}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{%- if cookiecutter.include_example_crud and cookiecutter.use_postgresql %}
|
|
2
|
+
"""Item database model - example CRUD entity."""
|
|
3
|
+
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import String, Text
|
|
7
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
9
|
+
|
|
10
|
+
from app.db.base import Base, TimestampMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Item(Base, TimestampMixin):
|
|
14
|
+
"""Item model - example entity for demonstrating CRUD operations.
|
|
15
|
+
|
|
16
|
+
This is a simple example model. You can use it as a template
|
|
17
|
+
for creating your own models or remove it if not needed.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__tablename__ = "items"
|
|
21
|
+
|
|
22
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
23
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
24
|
+
)
|
|
25
|
+
title: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
26
|
+
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
27
|
+
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"<Item(id={self.id}, title={self.title})>"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
{%- elif cookiecutter.include_example_crud and cookiecutter.use_sqlite %}
|
|
34
|
+
"""Item database model - example CRUD entity."""
|
|
35
|
+
|
|
36
|
+
import uuid
|
|
37
|
+
|
|
38
|
+
from sqlalchemy import Boolean, String, Text
|
|
39
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
40
|
+
|
|
41
|
+
from app.db.base import Base, TimestampMixin
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Item(Base, TimestampMixin):
|
|
45
|
+
"""Item model - example entity for demonstrating CRUD operations.
|
|
46
|
+
|
|
47
|
+
This is a simple example model. You can use it as a template
|
|
48
|
+
for creating your own models or remove it if not needed.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
__tablename__ = "items"
|
|
52
|
+
|
|
53
|
+
id: Mapped[str] = mapped_column(
|
|
54
|
+
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
55
|
+
)
|
|
56
|
+
title: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
57
|
+
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
58
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
return f"<Item(id={self.id}, title={self.title})>"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
{%- elif cookiecutter.include_example_crud and cookiecutter.use_mongodb %}
|
|
65
|
+
"""Item document model for MongoDB - example CRUD entity."""
|
|
66
|
+
|
|
67
|
+
from datetime import UTC, datetime
|
|
68
|
+
from typing import Optional
|
|
69
|
+
|
|
70
|
+
from beanie import Document
|
|
71
|
+
from pydantic import Field
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Item(Document):
|
|
75
|
+
"""Item document model - example entity for demonstrating CRUD operations.
|
|
76
|
+
|
|
77
|
+
This is a simple example model. You can use it as a template
|
|
78
|
+
for creating your own models or remove it if not needed.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
title: str = Field(max_length=255)
|
|
82
|
+
description: Optional[str] = None
|
|
83
|
+
is_active: bool = True
|
|
84
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
85
|
+
updated_at: Optional[datetime] = None
|
|
86
|
+
|
|
87
|
+
class Settings:
|
|
88
|
+
name = "items"
|
|
89
|
+
indexes = [
|
|
90
|
+
"title",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
{%- else %}
|
|
95
|
+
"""Item model - not configured."""
|
|
96
|
+
{%- endif %}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_session_management and cookiecutter.use_jwt %}
|
|
2
|
+
{%- if cookiecutter.use_postgresql %}
|
|
3
|
+
"""Session database model for tracking user sessions."""
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text
|
|
9
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
10
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
11
|
+
|
|
12
|
+
from app.db.base import Base
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Session(Base):
|
|
16
|
+
"""User session model for tracking active login sessions."""
|
|
17
|
+
|
|
18
|
+
__tablename__ = "sessions"
|
|
19
|
+
|
|
20
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
|
21
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
22
|
+
)
|
|
23
|
+
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
24
|
+
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
|
25
|
+
)
|
|
26
|
+
refresh_token_hash: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
27
|
+
device_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
28
|
+
device_type: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
29
|
+
ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True)
|
|
30
|
+
user_agent: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
31
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
32
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
33
|
+
DateTime(timezone=True), default=datetime.utcnow, nullable=False
|
|
34
|
+
)
|
|
35
|
+
last_used_at: Mapped[datetime] = mapped_column(
|
|
36
|
+
DateTime(timezone=True), default=datetime.utcnow, nullable=False
|
|
37
|
+
)
|
|
38
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
39
|
+
|
|
40
|
+
# Relationship
|
|
41
|
+
user = relationship("User", back_populates="sessions")
|
|
42
|
+
|
|
43
|
+
def __repr__(self) -> str:
|
|
44
|
+
return f"<Session(id={self.id}, user_id={self.user_id}, device={self.device_name})>"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
48
|
+
"""Session database model for tracking user sessions."""
|
|
49
|
+
|
|
50
|
+
import uuid
|
|
51
|
+
from datetime import datetime
|
|
52
|
+
|
|
53
|
+
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text
|
|
54
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
55
|
+
|
|
56
|
+
from app.db.base import Base
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Session(Base):
|
|
60
|
+
"""User session model for tracking active login sessions."""
|
|
61
|
+
|
|
62
|
+
__tablename__ = "sessions"
|
|
63
|
+
|
|
64
|
+
id: Mapped[str] = mapped_column(
|
|
65
|
+
String(36), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
66
|
+
)
|
|
67
|
+
user_id: Mapped[str] = mapped_column(
|
|
68
|
+
String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
|
69
|
+
)
|
|
70
|
+
refresh_token_hash: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
71
|
+
device_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
72
|
+
device_type: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
73
|
+
ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True)
|
|
74
|
+
user_agent: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
75
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
76
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
77
|
+
DateTime, default=datetime.utcnow, nullable=False
|
|
78
|
+
)
|
|
79
|
+
last_used_at: Mapped[datetime] = mapped_column(
|
|
80
|
+
DateTime, default=datetime.utcnow, nullable=False
|
|
81
|
+
)
|
|
82
|
+
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
83
|
+
|
|
84
|
+
# Relationship
|
|
85
|
+
user = relationship("User", back_populates="sessions")
|
|
86
|
+
|
|
87
|
+
def __repr__(self) -> str:
|
|
88
|
+
return f"<Session(id={self.id}, user_id={self.user_id}, device={self.device_name})>"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
{%- elif cookiecutter.use_mongodb %}
|
|
92
|
+
"""Session document model for tracking user sessions."""
|
|
93
|
+
|
|
94
|
+
from datetime import UTC, datetime
|
|
95
|
+
from typing import Optional
|
|
96
|
+
|
|
97
|
+
from beanie import Document, Link
|
|
98
|
+
from pydantic import Field
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Session(Document):
|
|
102
|
+
"""User session document for tracking active login sessions."""
|
|
103
|
+
|
|
104
|
+
user_id: str
|
|
105
|
+
refresh_token_hash: str
|
|
106
|
+
device_name: Optional[str] = None
|
|
107
|
+
device_type: Optional[str] = None
|
|
108
|
+
ip_address: Optional[str] = None
|
|
109
|
+
user_agent: Optional[str] = None
|
|
110
|
+
is_active: bool = True
|
|
111
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
112
|
+
last_used_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
113
|
+
expires_at: datetime
|
|
114
|
+
|
|
115
|
+
class Settings:
|
|
116
|
+
name = "sessions"
|
|
117
|
+
indexes = [
|
|
118
|
+
"user_id",
|
|
119
|
+
"refresh_token_hash",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
{%- endif %}
|
|
124
|
+
{%- else %}
|
|
125
|
+
"""Session model - not configured."""
|
|
126
|
+
{%- endif %}
|