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,266 @@
1
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite -%}
2
+ # ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
3
+ """
4
+ Seed database with sample data.
5
+
6
+ This command is useful for development and testing.
7
+ Uses random data generation - install faker for better data:
8
+ uv add faker --group dev
9
+ """
10
+
11
+ import asyncio
12
+ import random
13
+ import string
14
+
15
+ import click
16
+ from sqlalchemy import delete, select
17
+
18
+ from app.commands import command, info, success, warning
19
+
20
+ # Try to import Faker for better data generation
21
+ try:
22
+ from faker import Faker
23
+ fake = Faker()
24
+ HAS_FAKER = True
25
+ except ImportError:
26
+ HAS_FAKER = False
27
+ fake = None
28
+
29
+
30
+ def random_email() -> str:
31
+ """Generate a random email address."""
32
+ if HAS_FAKER:
33
+ return fake.email()
34
+ random_str = ''.join(random.choices(string.ascii_lowercase, k=8))
35
+ return f"{random_str}@example.com"
36
+
37
+
38
+ def random_name() -> str:
39
+ """Generate a random full name."""
40
+ if HAS_FAKER:
41
+ return fake.name()
42
+ first_names = ["John", "Jane", "Bob", "Alice", "Charlie", "Diana", "Eve", "Frank"]
43
+ last_names = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis"]
44
+ return f"{random.choice(first_names)} {random.choice(last_names)}"
45
+
46
+
47
+ def random_title() -> str:
48
+ """Generate a random item title."""
49
+ if HAS_FAKER:
50
+ return fake.sentence(nb_words=4).rstrip('.')
51
+ adjectives = ["Amazing", "Great", "Awesome", "Fantastic", "Incredible", "Beautiful"]
52
+ nouns = ["Widget", "Gadget", "Thing", "Product", "Item", "Object"]
53
+ return f"{random.choice(adjectives)} {random.choice(nouns)}"
54
+
55
+
56
+ def random_description() -> str:
57
+ """Generate a random description."""
58
+ if HAS_FAKER:
59
+ return fake.paragraph(nb_sentences=3)
60
+ return "This is a sample description for development purposes."
61
+
62
+
63
+ @command("seed", help="Seed database with sample data")
64
+ @click.option("--count", "-c", default=10, type=int, help="Number of records to create")
65
+ @click.option("--clear", is_flag=True, help="Clear existing data before seeding")
66
+ @click.option("--dry-run", is_flag=True, help="Show what would be created without making changes")
67
+ {%- if cookiecutter.use_jwt %}
68
+ @click.option("--users/--no-users", default=True, help="Seed users (default: True)")
69
+ {%- endif %}
70
+ {%- if cookiecutter.include_example_crud %}
71
+ @click.option("--items/--no-items", default=True, help="Seed items (default: True)")
72
+ {%- endif %}
73
+ def seed(
74
+ count: int,
75
+ clear: bool,
76
+ dry_run: bool,
77
+ {%- if cookiecutter.use_jwt %}
78
+ users: bool,
79
+ {%- endif %}
80
+ {%- if cookiecutter.include_example_crud %}
81
+ items: bool,
82
+ {%- endif %}
83
+ ) -> None:
84
+ """
85
+ Seed the database with sample data for development.
86
+
87
+ Example:
88
+ project cmd seed --count 50
89
+ project cmd seed --clear --count 100
90
+ project cmd seed --dry-run
91
+ {%- if cookiecutter.use_jwt %}
92
+ project cmd seed --no-users --items # Only seed items
93
+ {%- endif %}
94
+ """
95
+ if not HAS_FAKER:
96
+ warning("Faker not installed. Using basic random data. For better data: uv add faker --group dev")
97
+
98
+ if dry_run:
99
+ info(f"[DRY RUN] Would create {count} sample records per entity")
100
+ if clear:
101
+ info("[DRY RUN] Would clear existing data first")
102
+ {%- if cookiecutter.use_jwt %}
103
+ if users:
104
+ info("[DRY RUN] Would create users")
105
+ {%- endif %}
106
+ {%- if cookiecutter.include_example_crud %}
107
+ if items:
108
+ info("[DRY RUN] Would create items")
109
+ {%- endif %}
110
+ return
111
+
112
+ {%- if cookiecutter.use_postgresql %}
113
+ from app.db.session import async_session_maker
114
+ {%- if cookiecutter.use_jwt %}
115
+ from app.db.models.user import User
116
+ from app.core.security import get_password_hash
117
+ {%- endif %}
118
+ {%- if cookiecutter.include_example_crud %}
119
+ from app.db.models.item import Item
120
+ {%- endif %}
121
+
122
+ async def _seed():
123
+ async with async_session_maker() as session:
124
+ created_counts = {}
125
+
126
+ {%- if cookiecutter.use_jwt %}
127
+ # Seed users
128
+ if users:
129
+ if clear:
130
+ info("Clearing existing users (except superusers)...")
131
+ await session.execute(delete(User).where(User.is_superuser == False)) # noqa: E712
132
+ await session.commit()
133
+
134
+ # Check how many users already exist
135
+ result = await session.execute(select(User).limit(1))
136
+ existing = result.scalars().first()
137
+
138
+ if existing and not clear:
139
+ info("Users already exist. Use --clear to replace them.")
140
+ else:
141
+ info(f"Creating {count} sample users...")
142
+ for _ in range(count):
143
+ user = User(
144
+ email=random_email(),
145
+ hashed_password=get_password_hash("password123"),
146
+ full_name=random_name(),
147
+ is_active=True,
148
+ is_superuser=False,
149
+ role="user",
150
+ )
151
+ session.add(user)
152
+ await session.commit()
153
+ created_counts["users"] = count
154
+ {%- endif %}
155
+
156
+ {%- if cookiecutter.include_example_crud %}
157
+ # Seed items
158
+ if items:
159
+ if clear:
160
+ info("Clearing existing items...")
161
+ await session.execute(delete(Item))
162
+ await session.commit()
163
+
164
+ # Check how many items already exist
165
+ result = await session.execute(select(Item).limit(1))
166
+ existing = result.scalars().first()
167
+
168
+ if existing and not clear:
169
+ info("Items already exist. Use --clear to replace them.")
170
+ else:
171
+ info(f"Creating {count} sample items...")
172
+ for _ in range(count):
173
+ item = Item(
174
+ title=random_title(),
175
+ description=random_description(),
176
+ is_active=random.choice([True, True, True, False]), # 75% active
177
+ )
178
+ session.add(item)
179
+ await session.commit()
180
+ created_counts["items"] = count
181
+ {%- endif %}
182
+
183
+ if created_counts:
184
+ summary = ", ".join(f"{v} {k}" for k, v in created_counts.items())
185
+ success(f"Created: {summary}")
186
+ else:
187
+ info("No records created.")
188
+
189
+ asyncio.run(_seed())
190
+ {%- elif cookiecutter.use_sqlite %}
191
+ from app.db.session import SessionLocal
192
+ {%- if cookiecutter.use_jwt %}
193
+ from app.db.models.user import User
194
+ from app.core.security import get_password_hash
195
+ {%- endif %}
196
+ {%- if cookiecutter.include_example_crud %}
197
+ from app.db.models.item import Item
198
+ {%- endif %}
199
+
200
+ with SessionLocal() as session:
201
+ created_counts = {}
202
+
203
+ {%- if cookiecutter.use_jwt %}
204
+ # Seed users
205
+ if users:
206
+ if clear:
207
+ info("Clearing existing users (except superusers)...")
208
+ session.execute(delete(User).where(User.is_superuser == False)) # noqa: E712
209
+ session.commit()
210
+
211
+ # Check how many users already exist
212
+ result = session.execute(select(User).limit(1))
213
+ existing = result.scalars().first()
214
+
215
+ if existing and not clear:
216
+ info("Users already exist. Use --clear to replace them.")
217
+ else:
218
+ info(f"Creating {count} sample users...")
219
+ for _ in range(count):
220
+ user = User(
221
+ email=random_email(),
222
+ hashed_password=get_password_hash("password123"),
223
+ full_name=random_name(),
224
+ is_active=True,
225
+ is_superuser=False,
226
+ role="user",
227
+ )
228
+ session.add(user)
229
+ session.commit()
230
+ created_counts["users"] = count
231
+ {%- endif %}
232
+
233
+ {%- if cookiecutter.include_example_crud %}
234
+ # Seed items
235
+ if items:
236
+ if clear:
237
+ info("Clearing existing items...")
238
+ session.execute(delete(Item))
239
+ session.commit()
240
+
241
+ # Check how many items already exist
242
+ result = session.execute(select(Item).limit(1))
243
+ existing = result.scalars().first()
244
+
245
+ if existing and not clear:
246
+ info("Items already exist. Use --clear to replace them.")
247
+ else:
248
+ info(f"Creating {count} sample items...")
249
+ for _ in range(count):
250
+ item = Item(
251
+ title=random_title(),
252
+ description=random_description(),
253
+ is_active=random.choice([True, True, True, False]), # 75% active
254
+ )
255
+ session.add(item)
256
+ session.commit()
257
+ created_counts["items"] = count
258
+ {%- endif %}
259
+
260
+ if created_counts:
261
+ summary = ", ".join(f"{v} {k}" for k, v in created_counts.items())
262
+ success(f"Created: {summary}")
263
+ else:
264
+ info("No records created.")
265
+ {%- endif %}
266
+ {%- endif %}
@@ -0,0 +1,5 @@
1
+ """Core application configuration and utilities."""
2
+
3
+ from .config import settings
4
+
5
+ __all__ = ["settings"]
@@ -0,0 +1,23 @@
1
+ {%- if cookiecutter.enable_caching %}
2
+ """Caching configuration using fastapi-cache2."""
3
+
4
+ from fastapi_cache import FastAPICache
5
+ from fastapi_cache.backends.redis import RedisBackend
6
+
7
+ from app.clients.redis import RedisClient
8
+
9
+
10
+ def setup_cache(redis: RedisClient) -> None:
11
+ """Initialize FastAPI cache with Redis backend.
12
+
13
+ Uses the shared Redis client from lifespan state.
14
+ """
15
+ FastAPICache.init(RedisBackend(redis.raw), prefix="{{ cookiecutter.project_slug }}:cache:")
16
+ {%- else %}
17
+ """Caching - not configured."""
18
+
19
+
20
+ async def setup_cache() -> None:
21
+ """No-op when caching is disabled."""
22
+ pass
23
+ {%- endif %}
@@ -0,0 +1,267 @@
1
+ """Application configuration using Pydantic BaseSettings."""
2
+ {% if cookiecutter.use_database -%}
3
+ # ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
4
+ {% endif %}
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ {% if cookiecutter.use_database -%}
9
+ from pydantic import computed_field, field_validator{% if cookiecutter.use_jwt or cookiecutter.use_api_key or cookiecutter.enable_cors %}, ValidationInfo{% endif %}
10
+ {% else -%}
11
+ from pydantic import field_validator{% if cookiecutter.use_jwt or cookiecutter.use_api_key or cookiecutter.enable_cors %}, ValidationInfo{% endif %}
12
+ {% endif -%}
13
+ from pydantic_settings import BaseSettings, SettingsConfigDict
14
+
15
+
16
+ def find_env_file() -> Path | None:
17
+ """Find .env file in current or parent directories."""
18
+ current = Path.cwd()
19
+ for path in [current, current.parent]:
20
+ env_file = path / ".env"
21
+ if env_file.exists():
22
+ return env_file
23
+ return None
24
+
25
+
26
+ class Settings(BaseSettings):
27
+ """Application settings."""
28
+
29
+ model_config = SettingsConfigDict(
30
+ env_file=find_env_file(),
31
+ env_ignore_empty=True,
32
+ extra="ignore",
33
+ )
34
+
35
+ # === Project ===
36
+ PROJECT_NAME: str = "{{ cookiecutter.project_name }}"
37
+ API_V1_STR: str = "/api/v1"
38
+ DEBUG: bool = False
39
+ ENVIRONMENT: Literal["development", "local", "staging", "production"] = "local"
40
+
41
+ {%- if cookiecutter.enable_logfire %}
42
+
43
+ # === Logfire ===
44
+ LOGFIRE_TOKEN: str | None = None
45
+ LOGFIRE_SERVICE_NAME: str = "{{ cookiecutter.project_slug }}"
46
+ LOGFIRE_ENVIRONMENT: str = "development"
47
+ {%- endif %}
48
+
49
+ {%- if cookiecutter.use_postgresql %}
50
+
51
+ # === Database (PostgreSQL async) ===
52
+ POSTGRES_HOST: str = "localhost"
53
+ POSTGRES_PORT: int = 5432
54
+ POSTGRES_USER: str = "postgres"
55
+ POSTGRES_PASSWORD: str = ""
56
+ POSTGRES_DB: str = "{{ cookiecutter.project_slug }}"
57
+
58
+ @computed_field # type: ignore[prop-decorator]
59
+ @property
60
+ def DATABASE_URL(self) -> str:
61
+ """Build async PostgreSQL connection URL."""
62
+ return (
63
+ f"postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
64
+ f"@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
65
+ )
66
+
67
+ @computed_field # type: ignore[prop-decorator]
68
+ @property
69
+ def DATABASE_URL_SYNC(self) -> str:
70
+ """Build sync PostgreSQL connection URL (for Alembic)."""
71
+ return (
72
+ f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}"
73
+ f"@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
74
+ )
75
+
76
+ # Pool configuration
77
+ DB_POOL_SIZE: int = {{ cookiecutter.db_pool_size }}
78
+ DB_MAX_OVERFLOW: int = {{ cookiecutter.db_max_overflow }}
79
+ DB_POOL_TIMEOUT: int = {{ cookiecutter.db_pool_timeout }}
80
+ {%- endif %}
81
+
82
+ {%- if cookiecutter.use_mongodb %}
83
+
84
+ # === Database (MongoDB async) ===
85
+ MONGO_HOST: str = "localhost"
86
+ MONGO_PORT: int = 27017
87
+ MONGO_DB: str = "{{ cookiecutter.project_slug }}"
88
+ MONGO_USER: str | None = None
89
+ MONGO_PASSWORD: str | None = None
90
+
91
+ @computed_field # type: ignore[prop-decorator]
92
+ @property
93
+ def MONGO_URL(self) -> str:
94
+ """Build MongoDB connection URL."""
95
+ if self.MONGO_USER and self.MONGO_PASSWORD:
96
+ return f"mongodb://{self.MONGO_USER}:{self.MONGO_PASSWORD}@{self.MONGO_HOST}:{self.MONGO_PORT}"
97
+ return f"mongodb://{self.MONGO_HOST}:{self.MONGO_PORT}"
98
+ {%- endif %}
99
+
100
+ {%- if cookiecutter.use_sqlite %}
101
+
102
+ # === Database (SQLite sync) ===
103
+ SQLITE_PATH: str = "./{{ cookiecutter.project_slug }}.db"
104
+
105
+ @computed_field # type: ignore[prop-decorator]
106
+ @property
107
+ def DATABASE_URL(self) -> str:
108
+ """Build SQLite connection URL."""
109
+ return f"sqlite:///{self.SQLITE_PATH}"
110
+ {%- endif %}
111
+
112
+ {%- if cookiecutter.use_jwt %}
113
+
114
+ # === Auth (JWT) ===
115
+ SECRET_KEY: str = "change-me-in-production-use-openssl-rand-hex-32"
116
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 # 30 minutes
117
+ REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days
118
+ ALGORITHM: str = "HS256"
119
+
120
+ @field_validator("SECRET_KEY")
121
+ @classmethod
122
+ def validate_secret_key(cls, v: str, info: ValidationInfo) -> str:
123
+ """Validate SECRET_KEY is secure in production."""
124
+ if len(v) < 32:
125
+ raise ValueError("SECRET_KEY must be at least 32 characters long")
126
+ # Get environment from values if available
127
+ env = info.data.get("ENVIRONMENT", "local") if info.data else "local"
128
+ if v == "change-me-in-production-use-openssl-rand-hex-32" and env == "production":
129
+ raise ValueError(
130
+ "SECRET_KEY must be changed in production! "
131
+ "Generate a secure key with: openssl rand -hex 32"
132
+ )
133
+ return v
134
+ {%- endif %}
135
+
136
+ {%- if cookiecutter.enable_oauth_google %}
137
+
138
+ # === OAuth2 (Google) ===
139
+ GOOGLE_CLIENT_ID: str = ""
140
+ GOOGLE_CLIENT_SECRET: str = ""
141
+ GOOGLE_REDIRECT_URI: str = "http://localhost:{{ cookiecutter.backend_port }}/api/v1/oauth/google/callback"
142
+ {%- endif %}
143
+
144
+ {%- if cookiecutter.use_api_key %}
145
+
146
+ # === Auth (API Key) ===
147
+ API_KEY: str = "change-me-in-production"
148
+ API_KEY_HEADER: str = "X-API-Key"
149
+
150
+ @field_validator("API_KEY")
151
+ @classmethod
152
+ def validate_api_key(cls, v: str, info: ValidationInfo) -> str:
153
+ """Validate API_KEY is set in production."""
154
+ env = info.data.get("ENVIRONMENT", "local") if info.data else "local"
155
+ if v == "change-me-in-production" and env == "production":
156
+ raise ValueError(
157
+ "API_KEY must be changed in production! "
158
+ "Generate a secure key with: openssl rand -hex 32"
159
+ )
160
+ return v
161
+ {%- endif %}
162
+
163
+ {%- if cookiecutter.enable_redis %}
164
+
165
+ # === Redis ===
166
+ REDIS_HOST: str = "localhost"
167
+ REDIS_PORT: int = 6379
168
+ REDIS_PASSWORD: str | None = None
169
+ REDIS_DB: int = 0
170
+
171
+ @computed_field # type: ignore[prop-decorator]
172
+ @property
173
+ def REDIS_URL(self) -> str:
174
+ """Build Redis connection URL."""
175
+ if self.REDIS_PASSWORD:
176
+ return f"redis://:{self.REDIS_PASSWORD}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
177
+ return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
178
+ {%- endif %}
179
+
180
+ {%- if cookiecutter.enable_rate_limiting %}
181
+
182
+ # === Rate Limiting ===
183
+ RATE_LIMIT_REQUESTS: int = {{ cookiecutter.rate_limit_requests }}
184
+ RATE_LIMIT_PERIOD: int = {{ cookiecutter.rate_limit_period }} # seconds
185
+ {%- endif %}
186
+
187
+ {%- if cookiecutter.use_celery %}
188
+
189
+ # === Celery ===
190
+ CELERY_BROKER_URL: str = "redis://localhost:6379/0"
191
+ CELERY_RESULT_BACKEND: str = "redis://localhost:6379/0"
192
+ {%- endif %}
193
+
194
+ {%- if cookiecutter.use_taskiq %}
195
+
196
+ # === Taskiq ===
197
+ TASKIQ_BROKER_URL: str = "redis://localhost:6379/1"
198
+ TASKIQ_RESULT_BACKEND: str = "redis://localhost:6379/1"
199
+ {%- endif %}
200
+
201
+ {%- if cookiecutter.enable_sentry %}
202
+
203
+ # === Sentry ===
204
+ SENTRY_DSN: str | None = None
205
+ {%- endif %}
206
+
207
+ {%- if cookiecutter.enable_file_storage %}
208
+
209
+ # === File Storage (S3/MinIO) ===
210
+ S3_ENDPOINT: str | None = None
211
+ S3_ACCESS_KEY: str = ""
212
+ S3_SECRET_KEY: str = ""
213
+ S3_BUCKET: str = "{{ cookiecutter.project_slug }}"
214
+ S3_REGION: str = "us-east-1"
215
+ {%- endif %}
216
+
217
+ {%- if cookiecutter.enable_ai_agent %}
218
+
219
+ # === AI Agent ({{ cookiecutter.ai_framework }}, {{ cookiecutter.llm_provider }}) ===
220
+ {%- if cookiecutter.use_openai %}
221
+ OPENAI_API_KEY: str = ""
222
+ AI_MODEL: str = "gpt-4o-mini"
223
+ {%- endif %}
224
+ {%- if cookiecutter.use_anthropic %}
225
+ ANTHROPIC_API_KEY: str = ""
226
+ AI_MODEL: str = "claude-sonnet-4-5-20241022"
227
+ {%- endif %}
228
+ {%- if cookiecutter.use_openrouter %}
229
+ OPENROUTER_API_KEY: str = ""
230
+ AI_MODEL: str = "anthropic/claude-3.5-sonnet"
231
+ {%- endif %}
232
+ AI_TEMPERATURE: float = 0.7
233
+ AI_FRAMEWORK: str = "{{ cookiecutter.ai_framework }}"
234
+ LLM_PROVIDER: str = "{{ cookiecutter.llm_provider }}"
235
+ {%- if cookiecutter.use_langchain %}
236
+
237
+ # === LangSmith (LangChain observability) ===
238
+ LANGCHAIN_TRACING_V2: bool = True
239
+ LANGCHAIN_API_KEY: str | None = None
240
+ LANGCHAIN_PROJECT: str = "{{ cookiecutter.project_slug }}"
241
+ LANGCHAIN_ENDPOINT: str = "https://api.smith.langchain.com"
242
+ {%- endif %}
243
+ {%- endif %}
244
+
245
+ {%- if cookiecutter.enable_cors %}
246
+
247
+ # === CORS ===
248
+ CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:8080"]
249
+ CORS_ALLOW_CREDENTIALS: bool = True
250
+ CORS_ALLOW_METHODS: list[str] = ["*"]
251
+ CORS_ALLOW_HEADERS: list[str] = ["*"]
252
+
253
+ @field_validator("CORS_ORIGINS")
254
+ @classmethod
255
+ def validate_cors_origins(cls, v: list[str], info: ValidationInfo) -> list[str]:
256
+ """Warn if CORS_ORIGINS is too permissive in production."""
257
+ env = info.data.get("ENVIRONMENT", "local") if info.data else "local"
258
+ if "*" in v and env == "production":
259
+ raise ValueError(
260
+ "CORS_ORIGINS cannot contain '*' in production! "
261
+ "Specify explicit allowed origins."
262
+ )
263
+ return v
264
+ {%- endif %}
265
+
266
+
267
+ settings = Settings()