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.
Files changed (241) hide show
  1. fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
  2. fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
  3. fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +442 -0
  8. fastapi_gen/config.py +356 -0
  9. fastapi_gen/generator.py +207 -0
  10. fastapi_gen/prompts.py +874 -0
  11. fastapi_gen/template/VARIABLES.md +276 -0
  12. fastapi_gen/template/cookiecutter.json +93 -0
  13. fastapi_gen/template/hooks/post_gen_project.py +355 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  222. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  223. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  224. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
  225. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  226. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
  227. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
  228. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  229. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  230. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
  231. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
  232. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
  233. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  234. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  235. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  236. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
  237. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  238. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  239. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  240. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  241. 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 %}