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,153 @@
1
+ {%- if cookiecutter.use_jwt %}
2
+ """CSRF protection middleware for FastAPI.
3
+
4
+ This module provides CSRF (Cross-Site Request Forgery) protection for
5
+ state-changing HTTP methods (POST, PUT, PATCH, DELETE).
6
+
7
+ The protection works by:
8
+ 1. Setting a CSRF token in a cookie on initial request
9
+ 2. Requiring the token to be sent in a header for state-changing requests
10
+ 3. Comparing the cookie token with the header token
11
+
12
+ Usage:
13
+ Add to your main.py:
14
+
15
+ from app.core.csrf import CSRFMiddleware
16
+
17
+ app.add_middleware(CSRFMiddleware)
18
+
19
+ For endpoints that should be exempt (e.g., login):
20
+
21
+ @router.post("/login", tags=["csrf-exempt"])
22
+ async def login(...):
23
+ ...
24
+ """
25
+
26
+ import secrets
27
+ from collections.abc import Callable
28
+ from typing import ClassVar
29
+
30
+ from fastapi import Request, Response
31
+ from fastapi.responses import JSONResponse
32
+ from starlette.middleware.base import BaseHTTPMiddleware
33
+
34
+ from app.core.config import settings
35
+
36
+
37
+ class CSRFMiddleware(BaseHTTPMiddleware):
38
+ """CSRF protection middleware.
39
+
40
+ Protects against Cross-Site Request Forgery attacks by requiring
41
+ a token to be present in both a cookie and a header for state-changing requests.
42
+ """
43
+
44
+ # Methods that require CSRF protection
45
+ PROTECTED_METHODS: ClassVar[set[str]] = {"POST", "PUT", "PATCH", "DELETE"}
46
+
47
+ # Cookie settings
48
+ COOKIE_NAME: ClassVar[str] = "csrf_token"
49
+ HEADER_NAME: ClassVar[str] = "X-CSRF-Token"
50
+
51
+ # Paths to exclude from CSRF protection
52
+ EXEMPT_PATHS: ClassVar[set[str]] = {
53
+ "/api/v1/auth/login",
54
+ "/api/v1/auth/register",
55
+ "/api/v1/auth/refresh",
56
+ "/api/v1/health",
57
+ "/api/v1/ready",
58
+ "/docs",
59
+ "/openapi.json",
60
+ "/redoc",
61
+ }
62
+
63
+ def __init__(self, app: Callable, **kwargs):
64
+ super().__init__(app)
65
+ self.exempt_paths = set(kwargs.get("exempt_paths", self.EXEMPT_PATHS))
66
+ self.cookie_name = kwargs.get("cookie_name", self.COOKIE_NAME)
67
+ self.header_name = kwargs.get("header_name", self.HEADER_NAME)
68
+
69
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
70
+ """Handle the request and apply CSRF protection."""
71
+ # Skip for exempt paths
72
+ if self._is_exempt(request):
73
+ return await call_next(request)
74
+
75
+ # Get or generate CSRF token
76
+ csrf_token = request.cookies.get(self.cookie_name)
77
+ if not csrf_token:
78
+ csrf_token = self._generate_token()
79
+
80
+ # Check CSRF for protected methods
81
+ if request.method in self.PROTECTED_METHODS:
82
+ header_token = request.headers.get(self.header_name)
83
+
84
+ if not header_token:
85
+ return JSONResponse(
86
+ status_code=403,
87
+ content={
88
+ "detail": "CSRF token missing",
89
+ "message": f"Include the '{self.header_name}' header with the CSRF token",
90
+ },
91
+ )
92
+
93
+ if not secrets.compare_digest(csrf_token, header_token):
94
+ return JSONResponse(
95
+ status_code=403,
96
+ content={
97
+ "detail": "CSRF token invalid",
98
+ "message": "The CSRF token does not match",
99
+ },
100
+ )
101
+
102
+ # Process the request
103
+ response = await call_next(request)
104
+
105
+ # Set CSRF token cookie if not present
106
+ if not request.cookies.get(self.cookie_name):
107
+ response.set_cookie(
108
+ key=self.cookie_name,
109
+ value=csrf_token,
110
+ httponly=False, # JavaScript needs to read this
111
+ secure=not settings.DEBUG,
112
+ samesite="lax",
113
+ max_age=3600 * 24, # 24 hours
114
+ )
115
+
116
+ return response
117
+
118
+ def _is_exempt(self, request: Request) -> bool:
119
+ """Check if the request path is exempt from CSRF protection."""
120
+ path = request.url.path
121
+
122
+ # Check exact path matches
123
+ if path in self.exempt_paths:
124
+ return True
125
+
126
+ # Check path prefixes
127
+ for exempt in self.exempt_paths:
128
+ if path.startswith(exempt):
129
+ return True
130
+
131
+ # Check if endpoint has "csrf-exempt" tag
132
+ route = request.scope.get("route")
133
+ return bool(route and hasattr(route, "tags") and "csrf-exempt" in route.tags)
134
+
135
+ @staticmethod
136
+ def _generate_token() -> str:
137
+ """Generate a secure CSRF token."""
138
+ return secrets.token_urlsafe(32)
139
+
140
+
141
+ def get_csrf_token(request: Request) -> str:
142
+ """Get the current CSRF token from cookies or generate a new one.
143
+
144
+ Use this in templates or API responses to provide the token to clients.
145
+ """
146
+ token = request.cookies.get(CSRFMiddleware.COOKIE_NAME)
147
+ if not token:
148
+ token = secrets.token_urlsafe(32)
149
+ return token
150
+
151
+ {%- else %}
152
+ """CSRF protection - authentication not enabled."""
153
+ {%- endif %}
@@ -0,0 +1,122 @@
1
+ """Application exceptions.
2
+
3
+ Domain exceptions with HTTP status codes for the hybrid approach.
4
+ These exceptions are caught by exception handlers and converted to proper HTTP responses.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+
10
+ class AppException(Exception):
11
+ """Base exception for all application errors.
12
+
13
+ Attributes:
14
+ message: Human-readable error message.
15
+ code: Machine-readable error code for clients.
16
+ status_code: HTTP status code to return.
17
+ details: Additional error details (e.g., field names, IDs).
18
+ """
19
+
20
+ message: str = "An error occurred"
21
+ code: str = "APP_ERROR"
22
+ status_code: int = 500
23
+
24
+ def __init__(
25
+ self,
26
+ message: str | None = None,
27
+ code: str | None = None,
28
+ details: dict[str, Any] | None = None,
29
+ ):
30
+ self.message = message or self.__class__.message
31
+ self.code = code or self.__class__.code
32
+ self.details = details or {}
33
+ super().__init__(self.message)
34
+
35
+ def __repr__(self) -> str:
36
+ return f"{self.__class__.__name__}(message={self.message!r}, code={self.code!r})"
37
+
38
+
39
+ # === 4xx Client Errors ===
40
+
41
+
42
+ class NotFoundError(AppException):
43
+ """Resource not found (404)."""
44
+
45
+ message = "Resource not found"
46
+ code = "NOT_FOUND"
47
+ status_code = 404
48
+
49
+
50
+ class AlreadyExistsError(AppException):
51
+ """Resource already exists (409)."""
52
+
53
+ message = "Resource already exists"
54
+ code = "ALREADY_EXISTS"
55
+ status_code = 409
56
+
57
+
58
+ class ValidationError(AppException):
59
+ """Validation error (422)."""
60
+
61
+ message = "Validation error"
62
+ code = "VALIDATION_ERROR"
63
+ status_code = 422
64
+
65
+
66
+ class AuthenticationError(AppException):
67
+ """Authentication failed (401)."""
68
+
69
+ message = "Authentication failed"
70
+ code = "AUTHENTICATION_ERROR"
71
+ status_code = 401
72
+
73
+
74
+ class AuthorizationError(AppException):
75
+ """Authorization failed - insufficient permissions (403)."""
76
+
77
+ message = "Insufficient permissions"
78
+ code = "AUTHORIZATION_ERROR"
79
+ status_code = 403
80
+
81
+
82
+ class RateLimitError(AppException):
83
+ """Rate limit exceeded (429)."""
84
+
85
+ message = "Rate limit exceeded"
86
+ code = "RATE_LIMIT_EXCEEDED"
87
+ status_code = 429
88
+
89
+
90
+ class BadRequestError(AppException):
91
+ """Bad request (400)."""
92
+
93
+ message = "Bad request"
94
+ code = "BAD_REQUEST"
95
+ status_code = 400
96
+
97
+
98
+ # === 5xx Server Errors ===
99
+
100
+
101
+ class ExternalServiceError(AppException):
102
+ """External service unavailable (503)."""
103
+
104
+ message = "External service unavailable"
105
+ code = "EXTERNAL_SERVICE_ERROR"
106
+ status_code = 503
107
+
108
+
109
+ class DatabaseError(AppException):
110
+ """Database error (500)."""
111
+
112
+ message = "Database error"
113
+ code = "DATABASE_ERROR"
114
+ status_code = 500
115
+
116
+
117
+ class InternalError(AppException):
118
+ """Internal server error (500)."""
119
+
120
+ message = "Internal server error"
121
+ code = "INTERNAL_ERROR"
122
+ status_code = 500
@@ -0,0 +1,101 @@
1
+ {%- if cookiecutter.enable_logfire %}
2
+ """Logfire observability configuration."""
3
+
4
+ import logfire
5
+
6
+ from app.core.config import settings
7
+
8
+
9
+ def setup_logfire() -> None:
10
+ """Configure Logfire instrumentation."""
11
+ logfire.configure(
12
+ token=settings.LOGFIRE_TOKEN,
13
+ service_name=settings.LOGFIRE_SERVICE_NAME,
14
+ environment=settings.LOGFIRE_ENVIRONMENT,
15
+ send_to_logfire="if-token-present",
16
+ )
17
+
18
+
19
+ def instrument_app(app):
20
+ """Instrument FastAPI app with Logfire."""
21
+ {%- if cookiecutter.logfire_fastapi %}
22
+ logfire.instrument_fastapi(app)
23
+ {%- else %}
24
+ pass
25
+ {%- endif %}
26
+
27
+
28
+ {%- if cookiecutter.use_postgresql and cookiecutter.logfire_database %}
29
+
30
+
31
+ def instrument_asyncpg():
32
+ """Instrument asyncpg for PostgreSQL."""
33
+ logfire.instrument_asyncpg()
34
+ {%- endif %}
35
+
36
+
37
+ {%- if cookiecutter.use_mongodb and cookiecutter.logfire_database %}
38
+
39
+
40
+ def instrument_pymongo():
41
+ """Instrument PyMongo/Motor for MongoDB."""
42
+ logfire.instrument_pymongo(capture_statement=settings.DEBUG)
43
+ {%- endif %}
44
+
45
+
46
+ {%- if cookiecutter.use_sqlite and cookiecutter.logfire_database %}
47
+
48
+
49
+ def instrument_sqlalchemy(engine):
50
+ """Instrument SQLAlchemy for SQLite."""
51
+ logfire.instrument_sqlalchemy(engine=engine)
52
+ {%- endif %}
53
+
54
+
55
+ {%- if cookiecutter.enable_redis and cookiecutter.logfire_redis %}
56
+
57
+
58
+ def instrument_redis():
59
+ """Instrument Redis."""
60
+ logfire.instrument_redis()
61
+ {%- endif %}
62
+
63
+
64
+ {%- if cookiecutter.use_celery and cookiecutter.logfire_celery %}
65
+
66
+
67
+ def instrument_celery():
68
+ """Instrument Celery."""
69
+ logfire.instrument_celery()
70
+ {%- endif %}
71
+
72
+
73
+ {%- if cookiecutter.logfire_httpx %}
74
+
75
+
76
+ def instrument_httpx():
77
+ """Instrument HTTPX for outgoing HTTP requests."""
78
+ logfire.instrument_httpx()
79
+ {%- endif %}
80
+
81
+
82
+ {%- if cookiecutter.enable_ai_agent and cookiecutter.use_pydantic_ai %}
83
+
84
+
85
+ def instrument_pydantic_ai():
86
+ """Instrument PydanticAI for AI agent observability."""
87
+ logfire.instrument_pydantic_ai()
88
+ {%- endif %}
89
+ {%- else %}
90
+ """Logfire is disabled for this project."""
91
+
92
+
93
+ def setup_logfire() -> None:
94
+ """No-op when Logfire is disabled."""
95
+ pass
96
+
97
+
98
+ def instrument_app(app):
99
+ """No-op when Logfire is disabled."""
100
+ pass
101
+ {%- endif %}
@@ -0,0 +1,99 @@
1
+ """Application middleware."""
2
+
3
+ from typing import ClassVar
4
+ from uuid import uuid4
5
+
6
+ from starlette.middleware.base import BaseHTTPMiddleware
7
+ from starlette.requests import Request
8
+ from starlette.responses import Response
9
+
10
+
11
+ class RequestIDMiddleware(BaseHTTPMiddleware):
12
+ """Middleware that adds a unique request ID to each request.
13
+
14
+ The request ID is taken from the X-Request-ID header if present,
15
+ otherwise a new UUID is generated. The ID is added to the response
16
+ headers and is available in request.state.request_id.
17
+ """
18
+
19
+ async def dispatch(self, request: Request, call_next) -> Response:
20
+ """Add request ID to request state and response headers."""
21
+ request_id = request.headers.get("X-Request-ID", str(uuid4()))
22
+ request.state.request_id = request_id
23
+
24
+ response = await call_next(request)
25
+ response.headers["X-Request-ID"] = request_id
26
+ return response
27
+
28
+
29
+ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
30
+ """Middleware that adds security headers to all responses.
31
+
32
+ This includes:
33
+ - Content-Security-Policy (CSP)
34
+ - X-Content-Type-Options
35
+ - X-Frame-Options
36
+ - X-XSS-Protection
37
+ - Referrer-Policy
38
+ - Permissions-Policy
39
+
40
+ Usage:
41
+ app.add_middleware(SecurityHeadersMiddleware)
42
+
43
+ # Or with custom CSP:
44
+ app.add_middleware(
45
+ SecurityHeadersMiddleware,
46
+ csp_directives={
47
+ "default-src": "'self'",
48
+ "script-src": "'self' 'unsafe-inline'",
49
+ }
50
+ )
51
+ """
52
+
53
+ DEFAULT_CSP_DIRECTIVES: ClassVar[dict[str, str]] = {
54
+ "default-src": "'self'",
55
+ "script-src": "'self'",
56
+ "style-src": "'self' 'unsafe-inline'", # Allow inline styles for some UI libs
57
+ "img-src": "'self' data: https:",
58
+ "font-src": "'self' data:",
59
+ "connect-src": "'self'",
60
+ "frame-ancestors": "'none'",
61
+ "base-uri": "'self'",
62
+ "form-action": "'self'",
63
+ }
64
+
65
+ def __init__(
66
+ self,
67
+ app,
68
+ csp_directives: dict | None = None,
69
+ exclude_paths: set | None = None,
70
+ ):
71
+ super().__init__(app)
72
+ self.csp_directives = csp_directives or self.DEFAULT_CSP_DIRECTIVES
73
+ self.exclude_paths = exclude_paths or {"/docs", "/redoc", "/openapi.json"}
74
+
75
+ async def dispatch(self, request: Request, call_next) -> Response:
76
+ """Add security headers to the response."""
77
+ response = await call_next(request)
78
+
79
+ # Skip for docs/openapi endpoints which need different CSP
80
+ if request.url.path in self.exclude_paths:
81
+ return response
82
+
83
+ # Build CSP header
84
+ csp_value = "; ".join(
85
+ f"{directive} {value}" for directive, value in self.csp_directives.items()
86
+ )
87
+
88
+ # Add security headers
89
+ response.headers["Content-Security-Policy"] = csp_value
90
+ response.headers["X-Content-Type-Options"] = "nosniff"
91
+ response.headers["X-Frame-Options"] = "DENY"
92
+ response.headers["X-XSS-Protection"] = "1; mode=block"
93
+ response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
94
+ response.headers["Permissions-Policy"] = (
95
+ "accelerometer=(), camera=(), geolocation=(), gyroscope=(), "
96
+ "magnetometer=(), microphone=(), payment=(), usb=()"
97
+ )
98
+
99
+ return response
@@ -0,0 +1,23 @@
1
+ {%- if cookiecutter.enable_oauth %}
2
+ """OAuth2 client configuration."""
3
+
4
+ from authlib.integrations.starlette_client import OAuth
5
+
6
+ from app.core.config import settings
7
+
8
+ oauth = OAuth()
9
+
10
+ {%- if cookiecutter.enable_oauth_google %}
11
+
12
+ # Configure Google OAuth2
13
+ oauth.register(
14
+ name="google",
15
+ client_id=settings.GOOGLE_CLIENT_ID,
16
+ client_secret=settings.GOOGLE_CLIENT_SECRET,
17
+ server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
18
+ client_kwargs={"scope": "openid email profile"},
19
+ )
20
+ {%- endif %}
21
+ {%- else %}
22
+ """OAuth module - not configured."""
23
+ {%- endif %}
@@ -0,0 +1,58 @@
1
+ {%- if cookiecutter.enable_rate_limiting %}
2
+ """Rate limiting configuration using slowapi.
3
+
4
+ Default rate limit: {{ cookiecutter.rate_limit_requests }} requests per {{ cookiecutter.rate_limit_period }} seconds.
5
+ Override with RATE_LIMIT_REQUESTS and RATE_LIMIT_PERIOD environment variables.
6
+ """
7
+
8
+ from slowapi import Limiter
9
+ from slowapi.util import get_remote_address
10
+
11
+ from app.core.config import settings
12
+
13
+
14
+ def get_default_rate_limit() -> str:
15
+ """Get default rate limit string from settings.
16
+
17
+ Returns a rate limit string like "100/minute" or "60/second".
18
+ """
19
+ requests = settings.RATE_LIMIT_REQUESTS
20
+ period = settings.RATE_LIMIT_PERIOD
21
+
22
+ # Convert period to a human-readable format
23
+ period_map = {
24
+ 60: "minute",
25
+ 3600: "hour",
26
+ 86400: "day",
27
+ }
28
+
29
+ if period in period_map:
30
+ return f"{requests}/{period_map[period]}"
31
+ # For custom periods, use "per X seconds"
32
+ return f"{requests}/{period} seconds"
33
+
34
+
35
+ # Rate limiter instance with configurable default
36
+ limiter = Limiter(
37
+ key_func=get_remote_address,
38
+ default_limits=[get_default_rate_limit()],
39
+ )
40
+
41
+ # Common rate limit decorators for convenience
42
+ # Usage: @rate_limit_low, @rate_limit_medium, @rate_limit_high
43
+ def rate_limit_low(limit: str = "10/minute"):
44
+ """Low rate limit for expensive operations."""
45
+ return limiter.limit(limit)
46
+
47
+
48
+ def rate_limit_medium(limit: str = "30/minute"):
49
+ """Medium rate limit for standard operations."""
50
+ return limiter.limit(limit)
51
+
52
+
53
+ def rate_limit_high(limit: str = "100/minute"):
54
+ """High rate limit for lightweight operations."""
55
+ return limiter.limit(limit)
56
+ {%- else %}
57
+ """Rate limiting - not configured."""
58
+ {%- endif %}