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,541 @@
1
+ """API dependencies.
2
+
3
+ Dependency injection factories for services, repositories, and authentication.
4
+ """
5
+ {%- if cookiecutter.use_database or cookiecutter.use_jwt or cookiecutter.use_api_key or cookiecutter.enable_redis %}
6
+ # ruff: noqa: I001, E402 - Imports structured for Jinja2 template conditionals
7
+ {%- endif %}
8
+ {%- if cookiecutter.use_database or cookiecutter.use_jwt or cookiecutter.use_api_key or cookiecutter.enable_redis %}
9
+
10
+ from typing import Annotated
11
+
12
+ from fastapi import Depends
13
+ {%- endif %}
14
+ {%- if cookiecutter.use_jwt %}
15
+ from fastapi.security import OAuth2PasswordBearer
16
+ {%- endif %}
17
+ {%- if cookiecutter.use_jwt or cookiecutter.use_api_key %}
18
+
19
+ from app.core.config import settings
20
+ {%- endif %}
21
+ {%- if cookiecutter.use_database %}
22
+ from app.db.session import get_db_session
23
+ {%- endif %}
24
+
25
+ {%- if cookiecutter.use_postgresql %}
26
+ from sqlalchemy.ext.asyncio import AsyncSession
27
+
28
+ DBSession = Annotated[AsyncSession, Depends(get_db_session)]
29
+ {%- endif %}
30
+
31
+ {%- if cookiecutter.use_sqlite %}
32
+ from sqlalchemy.orm import Session
33
+
34
+ DBSession = Annotated[Session, Depends(get_db_session)]
35
+ {%- endif %}
36
+
37
+ {%- if cookiecutter.use_mongodb %}
38
+ from motor.motor_asyncio import AsyncIOMotorDatabase
39
+
40
+ DBSession = Annotated[AsyncIOMotorDatabase, Depends(get_db_session)]
41
+ {%- endif %}
42
+
43
+ {%- if cookiecutter.enable_redis %}
44
+ from fastapi import Request
45
+
46
+ from app.clients.redis import RedisClient
47
+
48
+
49
+ async def get_redis(request: Request) -> RedisClient:
50
+ """Get Redis client from lifespan state."""
51
+ return request.state.redis
52
+
53
+
54
+ Redis = Annotated[RedisClient, Depends(get_redis)]
55
+ {%- endif %}
56
+
57
+ {%- if cookiecutter.use_jwt %}
58
+
59
+
60
+ # === Service Dependencies ===
61
+
62
+ from app.services.user import UserService
63
+ {%- if cookiecutter.enable_session_management %}
64
+ from app.services.session import SessionService
65
+ {%- endif %}
66
+ {%- endif %}
67
+ {%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
68
+ from app.services.webhook import WebhookService
69
+ {%- endif %}
70
+ {%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
71
+ from app.services.item import ItemService
72
+ {%- endif %}
73
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
74
+ from app.services.conversation import ConversationService
75
+ {%- endif %}
76
+ {%- if cookiecutter.use_jwt %}
77
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
78
+
79
+
80
+ def get_user_service(db: DBSession) -> UserService:
81
+ """Create UserService instance with database session."""
82
+ return UserService(db)
83
+
84
+ {%- if cookiecutter.enable_session_management %}
85
+
86
+
87
+ def get_session_service(db: DBSession) -> SessionService:
88
+ """Create SessionService instance with database session."""
89
+ return SessionService(db)
90
+ {%- endif %}
91
+ {%- elif cookiecutter.use_mongodb %}
92
+
93
+
94
+ def get_user_service() -> UserService:
95
+ """Create UserService instance."""
96
+ return UserService()
97
+
98
+ {%- if cookiecutter.enable_session_management %}
99
+
100
+
101
+ def get_session_service() -> SessionService:
102
+ """Create SessionService instance."""
103
+ return SessionService()
104
+ {%- endif %}
105
+ {%- endif %}
106
+
107
+
108
+ UserSvc = Annotated[UserService, Depends(get_user_service)]
109
+ {%- if cookiecutter.enable_session_management %}
110
+ SessionSvc = Annotated[SessionService, Depends(get_session_service)]
111
+ {%- endif %}
112
+ {%- endif %}
113
+
114
+ {%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
115
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
116
+
117
+
118
+ def get_webhook_service(db: DBSession) -> WebhookService:
119
+ """Create WebhookService instance with database session."""
120
+ return WebhookService(db)
121
+ {%- elif cookiecutter.use_mongodb %}
122
+
123
+
124
+ def get_webhook_service() -> WebhookService:
125
+ """Create WebhookService instance."""
126
+ return WebhookService()
127
+ {%- endif %}
128
+
129
+
130
+ WebhookSvc = Annotated[WebhookService, Depends(get_webhook_service)]
131
+ {%- endif %}
132
+
133
+ {%- if cookiecutter.include_example_crud and cookiecutter.use_database %}
134
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
135
+
136
+
137
+ def get_item_service(db: DBSession) -> ItemService:
138
+ """Create ItemService instance with database session."""
139
+ return ItemService(db)
140
+ {%- elif cookiecutter.use_mongodb %}
141
+
142
+
143
+ def get_item_service() -> ItemService:
144
+ """Create ItemService instance."""
145
+ return ItemService()
146
+ {%- endif %}
147
+
148
+
149
+ ItemSvc = Annotated[ItemService, Depends(get_item_service)]
150
+ {%- endif %}
151
+
152
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
153
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
154
+
155
+
156
+ def get_conversation_service(db: DBSession) -> ConversationService:
157
+ """Create ConversationService instance with database session."""
158
+ return ConversationService(db)
159
+ {%- elif cookiecutter.use_mongodb %}
160
+
161
+
162
+ def get_conversation_service() -> ConversationService:
163
+ """Create ConversationService instance."""
164
+ return ConversationService()
165
+ {%- endif %}
166
+
167
+
168
+ ConversationSvc = Annotated[ConversationService, Depends(get_conversation_service)]
169
+ {%- endif %}
170
+
171
+ {%- if cookiecutter.use_jwt %}
172
+
173
+ # === Authentication Dependencies ===
174
+
175
+ from app.core.exceptions import AuthenticationError, AuthorizationError
176
+ from app.db.models.user import User, UserRole
177
+
178
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
179
+
180
+ {%- if cookiecutter.use_postgresql %}
181
+
182
+
183
+ async def get_current_user(
184
+ token: Annotated[str, Depends(oauth2_scheme)],
185
+ user_service: UserSvc,
186
+ ) -> User:
187
+ """Get current authenticated user from JWT token.
188
+
189
+ Returns the full User object including role information.
190
+
191
+ Raises:
192
+ AuthenticationError: If token is invalid or user not found.
193
+ """
194
+ from uuid import UUID
195
+
196
+ from app.core.security import verify_token
197
+
198
+ payload = verify_token(token)
199
+ if payload is None:
200
+ raise AuthenticationError(message="Invalid or expired token")
201
+
202
+ # Ensure this is an access token, not a refresh token
203
+ if payload.get("type") != "access":
204
+ raise AuthenticationError(message="Invalid token type")
205
+
206
+ user_id = payload.get("sub")
207
+ if user_id is None:
208
+ raise AuthenticationError(message="Invalid token payload")
209
+
210
+ user = await user_service.get_by_id(UUID(user_id))
211
+ if not user.is_active:
212
+ raise AuthenticationError(message="User account is disabled")
213
+
214
+ return user
215
+
216
+
217
+ class RoleChecker:
218
+ """Dependency class for role-based access control.
219
+
220
+ Usage:
221
+ # Require admin role
222
+ @router.get("/admin-only")
223
+ async def admin_endpoint(
224
+ user: Annotated[User, Depends(RoleChecker(UserRole.ADMIN))]
225
+ ):
226
+ ...
227
+
228
+ # Require any authenticated user
229
+ @router.get("/users")
230
+ async def users_endpoint(
231
+ user: Annotated[User, Depends(get_current_user)]
232
+ ):
233
+ ...
234
+ """
235
+
236
+ def __init__(self, required_role: UserRole) -> None:
237
+ self.required_role = required_role
238
+
239
+ async def __call__(
240
+ self,
241
+ user: Annotated[User, Depends(get_current_user)],
242
+ ) -> User:
243
+ """Check if user has the required role.
244
+
245
+ Raises:
246
+ AuthorizationError: If user doesn't have the required role.
247
+ """
248
+ if not user.has_role(self.required_role):
249
+ raise AuthorizationError(
250
+ message=f"Role '{self.required_role.value}' required for this action"
251
+ )
252
+ return user
253
+
254
+
255
+ async def get_current_active_superuser(
256
+ current_user: Annotated[User, Depends(get_current_user)],
257
+ ) -> User:
258
+ """Get current user and verify they are a superuser.
259
+
260
+ Raises:
261
+ AuthorizationError: If user is not a superuser.
262
+ """
263
+ if not current_user.is_superuser:
264
+ raise AuthorizationError(message="Superuser privileges required")
265
+ return current_user
266
+ {%- elif cookiecutter.use_sqlite %}
267
+
268
+
269
+ def get_current_user(
270
+ token: Annotated[str, Depends(oauth2_scheme)],
271
+ user_service: UserSvc,
272
+ ) -> User:
273
+ """Get current authenticated user from JWT token.
274
+
275
+ Returns the full User object including role information.
276
+
277
+ Raises:
278
+ AuthenticationError: If token is invalid or user not found.
279
+ """
280
+ from app.core.security import verify_token
281
+
282
+ payload = verify_token(token)
283
+ if payload is None:
284
+ raise AuthenticationError(message="Invalid or expired token")
285
+
286
+ # Ensure this is an access token, not a refresh token
287
+ if payload.get("type") != "access":
288
+ raise AuthenticationError(message="Invalid token type")
289
+
290
+ user_id = payload.get("sub")
291
+ if user_id is None:
292
+ raise AuthenticationError(message="Invalid token payload")
293
+
294
+ user = user_service.get_by_id(user_id)
295
+ if not user.is_active:
296
+ raise AuthenticationError(message="User account is disabled")
297
+
298
+ return user
299
+
300
+
301
+ class RoleChecker:
302
+ """Dependency class for role-based access control.
303
+
304
+ Usage:
305
+ # Require admin role
306
+ @router.get("/admin-only")
307
+ def admin_endpoint(
308
+ user: Annotated[User, Depends(RoleChecker(UserRole.ADMIN))]
309
+ ):
310
+ ...
311
+
312
+ # Require any authenticated user
313
+ @router.get("/users")
314
+ def users_endpoint(
315
+ user: Annotated[User, Depends(get_current_user)]
316
+ ):
317
+ ...
318
+ """
319
+
320
+ def __init__(self, required_role: UserRole) -> None:
321
+ self.required_role = required_role
322
+
323
+ def __call__(
324
+ self,
325
+ user: Annotated[User, Depends(get_current_user)],
326
+ ) -> User:
327
+ """Check if user has the required role.
328
+
329
+ Raises:
330
+ AuthorizationError: If user doesn't have the required role.
331
+ """
332
+ if not user.has_role(self.required_role):
333
+ raise AuthorizationError(
334
+ message=f"Role '{self.required_role.value}' required for this action"
335
+ )
336
+ return user
337
+
338
+
339
+ def get_current_active_superuser(
340
+ current_user: Annotated[User, Depends(get_current_user)],
341
+ ) -> User:
342
+ """Get current user and verify they are a superuser.
343
+
344
+ Raises:
345
+ AuthorizationError: If user is not a superuser.
346
+ """
347
+ if not current_user.is_superuser:
348
+ raise AuthorizationError(message="Superuser privileges required")
349
+ return current_user
350
+ {%- elif cookiecutter.use_mongodb %}
351
+
352
+
353
+ async def get_current_user(
354
+ token: Annotated[str, Depends(oauth2_scheme)],
355
+ user_service: UserSvc,
356
+ ) -> User:
357
+ """Get current authenticated user from JWT token.
358
+
359
+ Returns the full User object including role information.
360
+
361
+ Raises:
362
+ AuthenticationError: If token is invalid or user not found.
363
+ """
364
+ from app.core.security import verify_token
365
+
366
+ payload = verify_token(token)
367
+ if payload is None:
368
+ raise AuthenticationError(message="Invalid or expired token")
369
+
370
+ # Ensure this is an access token, not a refresh token
371
+ if payload.get("type") != "access":
372
+ raise AuthenticationError(message="Invalid token type")
373
+
374
+ user_id = payload.get("sub")
375
+ if user_id is None:
376
+ raise AuthenticationError(message="Invalid token payload")
377
+
378
+ user = await user_service.get_by_id(user_id)
379
+ if not user.is_active:
380
+ raise AuthenticationError(message="User account is disabled")
381
+
382
+ return user
383
+
384
+
385
+ class RoleChecker:
386
+ """Dependency class for role-based access control.
387
+
388
+ Usage:
389
+ # Require admin role
390
+ @router.get("/admin-only")
391
+ async def admin_endpoint(
392
+ user: Annotated[User, Depends(RoleChecker(UserRole.ADMIN))]
393
+ ):
394
+ ...
395
+
396
+ # Require any authenticated user
397
+ @router.get("/users")
398
+ async def users_endpoint(
399
+ user: Annotated[User, Depends(get_current_user)]
400
+ ):
401
+ ...
402
+ """
403
+
404
+ def __init__(self, required_role: UserRole) -> None:
405
+ self.required_role = required_role
406
+
407
+ async def __call__(
408
+ self,
409
+ user: Annotated[User, Depends(get_current_user)],
410
+ ) -> User:
411
+ """Check if user has the required role.
412
+
413
+ Raises:
414
+ AuthorizationError: If user doesn't have the required role.
415
+ """
416
+ if not user.has_role(self.required_role):
417
+ raise AuthorizationError(
418
+ message=f"Role '{self.required_role.value}' required for this action"
419
+ )
420
+ return user
421
+
422
+
423
+ async def get_current_active_superuser(
424
+ current_user: Annotated[User, Depends(get_current_user)],
425
+ ) -> User:
426
+ """Get current user and verify they are a superuser.
427
+
428
+ Raises:
429
+ AuthorizationError: If user is not a superuser.
430
+ """
431
+ if not current_user.is_superuser:
432
+ raise AuthorizationError(message="Superuser privileges required")
433
+ return current_user
434
+ {%- endif %}
435
+
436
+
437
+ # Type aliases for dependency injection
438
+ CurrentUser = Annotated[User, Depends(get_current_user)]
439
+ CurrentSuperuser = Annotated[User, Depends(get_current_active_superuser)]
440
+ CurrentAdmin = Annotated[User, Depends(RoleChecker(UserRole.ADMIN))]
441
+
442
+
443
+ # WebSocket authentication dependency
444
+ from fastapi import WebSocket, Query, Cookie
445
+
446
+
447
+ async def get_current_user_ws(
448
+ websocket: WebSocket,
449
+ token: str | None = Query(None, alias="token"),
450
+ access_token: str | None = Cookie(None),
451
+ ) -> User:
452
+ """Get current user from WebSocket JWT token.
453
+
454
+ Token can be passed either as:
455
+ - Query parameter: ws://...?token=<jwt>
456
+ - Cookie: access_token cookie (set by HTTP login)
457
+
458
+ Raises:
459
+ AuthenticationError: If token is invalid or user not found.
460
+ """
461
+ from uuid import UUID
462
+
463
+ from app.core.security import verify_token
464
+
465
+ # Try query parameter first, then cookie
466
+ auth_token = token or access_token
467
+
468
+ if not auth_token:
469
+ await websocket.close(code=4001, reason="Missing authentication token")
470
+ raise AuthenticationError(message="Missing authentication token")
471
+
472
+ payload = verify_token(auth_token)
473
+ if payload is None:
474
+ await websocket.close(code=4001, reason="Invalid or expired token")
475
+ raise AuthenticationError(message="Invalid or expired token")
476
+
477
+ if payload.get("type") != "access":
478
+ await websocket.close(code=4001, reason="Invalid token type")
479
+ raise AuthenticationError(message="Invalid token type")
480
+
481
+ user_id = payload.get("sub")
482
+ if user_id is None:
483
+ await websocket.close(code=4001, reason="Invalid token payload")
484
+ raise AuthenticationError(message="Invalid token payload")
485
+ {%- if cookiecutter.use_postgresql %}
486
+
487
+ from app.db.session import get_db_context
488
+
489
+ async with get_db_context() as db:
490
+ user_service = UserService(db)
491
+ user = await user_service.get_by_id(UUID(user_id))
492
+ {%- elif cookiecutter.use_mongodb %}
493
+
494
+ db = await get_db_session()
495
+ user_service = UserService(db)
496
+ user = await user_service.get_by_id(UUID(user_id))
497
+ {%- elif cookiecutter.use_sqlite %}
498
+
499
+ with get_db_session() as db:
500
+ user_service = UserService(db)
501
+ user = user_service.get_by_id(user_id)
502
+ {%- endif %}
503
+
504
+ if not user.is_active:
505
+ await websocket.close(code=4001, reason="User account is disabled")
506
+ raise AuthenticationError(message="User account is disabled")
507
+
508
+ return user
509
+ {%- endif %}
510
+
511
+ {%- if cookiecutter.use_api_key %}
512
+
513
+ import secrets
514
+
515
+ from fastapi.security import APIKeyHeader
516
+
517
+ from app.core.exceptions import AuthenticationError, AuthorizationError
518
+
519
+ api_key_header = APIKeyHeader(name=settings.API_KEY_HEADER, auto_error=False)
520
+
521
+
522
+ async def verify_api_key(
523
+ api_key: Annotated[str | None, Depends(api_key_header)],
524
+ ) -> str:
525
+ """Verify API key from header.
526
+
527
+ Uses constant-time comparison to prevent timing attacks.
528
+
529
+ Raises:
530
+ AuthenticationError: If API key is missing.
531
+ AuthorizationError: If API key is invalid.
532
+ """
533
+ if api_key is None:
534
+ raise AuthenticationError(message="API Key header missing")
535
+ if not secrets.compare_digest(api_key, settings.API_KEY):
536
+ raise AuthorizationError(message="Invalid API Key")
537
+ return api_key
538
+
539
+
540
+ ValidAPIKey = Annotated[str, Depends(verify_api_key)]
541
+ {%- endif %}
@@ -0,0 +1,98 @@
1
+ """Exception handlers for FastAPI application.
2
+
3
+ These handlers convert domain exceptions to proper HTTP responses.
4
+ """
5
+
6
+ import logging
7
+ from typing import Union
8
+
9
+ from fastapi import FastAPI, Request, WebSocket
10
+ from fastapi.responses import JSONResponse
11
+
12
+ from app.core.exceptions import AppException
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ async def app_exception_handler(
18
+ request: Union[Request, WebSocket], exc: AppException
19
+ ) -> JSONResponse:
20
+ """Handle application exceptions.
21
+
22
+ Logs 5xx errors as errors and 4xx as warnings.
23
+ Returns a standardized JSON error response.
24
+
25
+ Note: For WebSocket connections, this handler may not be able to return
26
+ a response if the connection was already closed.
27
+ """
28
+ # WebSocket objects don't have a method attribute
29
+ method = getattr(request, "method", "WEBSOCKET")
30
+
31
+ log_extra = {
32
+ "error_code": exc.code,
33
+ "status_code": exc.status_code,
34
+ "details": exc.details,
35
+ "path": request.url.path,
36
+ "method": method,
37
+ }
38
+
39
+ if exc.status_code >= 500:
40
+ logger.error(f"{exc.code}: {exc.message}", extra=log_extra)
41
+ else:
42
+ logger.warning(f"{exc.code}: {exc.message}", extra=log_extra)
43
+
44
+ headers: dict[str, str] = {}
45
+ if exc.status_code == 401:
46
+ headers["WWW-Authenticate"] = "Bearer"
47
+
48
+ return JSONResponse(
49
+ status_code=exc.status_code,
50
+ content={
51
+ "error": {
52
+ "code": exc.code,
53
+ "message": exc.message,
54
+ "details": exc.details or None,
55
+ }
56
+ },
57
+ headers=headers,
58
+ )
59
+
60
+
61
+ async def unhandled_exception_handler(
62
+ request: Union[Request, WebSocket], exc: Exception
63
+ ) -> JSONResponse:
64
+ """Handle unexpected exceptions.
65
+
66
+ Logs the full exception but returns a generic error to the client
67
+ to avoid leaking sensitive information.
68
+ """
69
+ method = getattr(request, "method", "WEBSOCKET")
70
+
71
+ logger.exception(
72
+ "Unhandled exception",
73
+ extra={
74
+ "path": request.url.path,
75
+ "method": method,
76
+ },
77
+ )
78
+
79
+ return JSONResponse(
80
+ status_code=500,
81
+ content={
82
+ "error": {
83
+ "code": "INTERNAL_ERROR",
84
+ "message": "An unexpected error occurred",
85
+ "details": None,
86
+ }
87
+ },
88
+ )
89
+
90
+
91
+ def register_exception_handlers(app: FastAPI) -> None:
92
+ """Register all exception handlers on the FastAPI app.
93
+
94
+ Call this after creating the FastAPI application instance.
95
+ """
96
+ app.add_exception_handler(AppException, app_exception_handler)
97
+ # Uncomment to catch all unhandled exceptions:
98
+ # app.add_exception_handler(Exception, unhandled_exception_handler)
@@ -0,0 +1,10 @@
1
+ """API router aggregation."""
2
+
3
+ from fastapi import APIRouter
4
+
5
+ from app.api.routes.v1 import v1_router
6
+
7
+ api_router = APIRouter()
8
+
9
+ # API v1 routes (prefix is set in main.py via settings.API_V1_STR)
10
+ api_router.include_router(v1_router)
@@ -0,0 +1,9 @@
1
+ """API routes.
2
+
3
+ This package contains versioned API routes.
4
+ Add new versions by creating new folders (e.g., v2/) and updating router.py.
5
+ """
6
+
7
+ from app.api.routes.v1 import v1_router
8
+
9
+ __all__ = ["v1_router"]