fastapi-fullstack 0.1.2__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 (221) hide show
  1. fastapi_fullstack-0.1.2.dist-info/METADATA +545 -0
  2. fastapi_fullstack-0.1.2.dist-info/RECORD +221 -0
  3. fastapi_fullstack-0.1.2.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.2.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.2.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +256 -0
  8. fastapi_gen/config.py +255 -0
  9. fastapi_gen/generator.py +181 -0
  10. fastapi_gen/prompts.py +648 -0
  11. fastapi_gen/template/cookiecutter.json +76 -0
  12. fastapi_gen/template/hooks/post_gen_project.py +111 -0
  13. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.example +136 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +108 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +357 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +298 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +723 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +115 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +13 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +202 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +528 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +85 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +448 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +490 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +247 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +113 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +326 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +760 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +195 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +797 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +158 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +121 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +382 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +693 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +66 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/layout.tsx +11 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/login/page.tsx +5 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(auth)/register/page.tsx +5 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/chat/page.tsx +20 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/dashboard/page.tsx +99 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/layout.tsx +17 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/(dashboard)/profile/page.tsx +156 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/auth/callback/page.tsx +96 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +108 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +25 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +135 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +73 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +261 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +8 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +63 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +60 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +45 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +48 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +83 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +54 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +12 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +6 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +175 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +32 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +33 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +48 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +6 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +81 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
@@ -0,0 +1,490 @@
1
+ {%- if cookiecutter.enable_conversation_persistence %}
2
+ """Conversation API routes for AI chat persistence.
3
+
4
+ Provides CRUD operations for conversations and messages.
5
+
6
+ The endpoints are:
7
+ - GET /conversations - List user's conversations
8
+ - POST /conversations - Create a new conversation
9
+ - GET /conversations/{id} - Get a conversation with messages
10
+ - PATCH /conversations/{id} - Update conversation title/archived status
11
+ - DELETE /conversations/{id} - Delete a conversation
12
+ - POST /conversations/{id}/messages - Add a message to conversation
13
+ - GET /conversations/{id}/messages - List messages in conversation
14
+ """
15
+
16
+ {%- if cookiecutter.use_postgresql %}
17
+ from uuid import UUID
18
+ {%- endif %}
19
+
20
+ from fastapi import APIRouter, Query, status
21
+
22
+ {%- if cookiecutter.use_mongodb %}
23
+ from app.api.deps import ConversationSvc
24
+ {%- else %}
25
+ from app.api.deps import DBSession, ConversationSvc
26
+ {%- endif %}
27
+ {%- if cookiecutter.use_jwt %}
28
+ from app.api.deps import CurrentUser
29
+ {%- endif %}
30
+ from app.schemas.conversation import (
31
+ ConversationCreate,
32
+ ConversationRead,
33
+ ConversationReadWithMessages,
34
+ ConversationUpdate,
35
+ MessageCreate,
36
+ MessageRead,
37
+ MessageReadSimple,
38
+ )
39
+
40
+ router = APIRouter()
41
+
42
+
43
+ {%- if cookiecutter.use_postgresql %}
44
+
45
+
46
+ @router.get("", response_model=list[ConversationRead])
47
+ async def list_conversations(
48
+ conversation_service: ConversationSvc,
49
+ {%- if cookiecutter.use_jwt %}
50
+ current_user: CurrentUser,
51
+ {%- endif %}
52
+ skip: int = Query(0, ge=0, description="Number of conversations to skip"),
53
+ limit: int = Query(50, ge=1, le=100, description="Maximum conversations to return"),
54
+ include_archived: bool = Query(False, description="Include archived conversations"),
55
+ ):
56
+ """List conversations for the current user.
57
+
58
+ Returns conversations ordered by most recently updated.
59
+ """
60
+ return await conversation_service.list_conversations(
61
+ {%- if cookiecutter.use_jwt %}
62
+ user_id=current_user.id,
63
+ {%- endif %}
64
+ skip=skip,
65
+ limit=limit,
66
+ include_archived=include_archived,
67
+ )
68
+
69
+
70
+ @router.post("", response_model=ConversationRead, status_code=status.HTTP_201_CREATED)
71
+ async def create_conversation(
72
+ conversation_service: ConversationSvc,
73
+ {%- if cookiecutter.use_jwt %}
74
+ current_user: CurrentUser,
75
+ {%- endif %}
76
+ data: ConversationCreate | None = None,
77
+ ):
78
+ """Create a new conversation.
79
+
80
+ The title is optional and can be set later.
81
+ """
82
+ if data is None:
83
+ data = ConversationCreate()
84
+ {%- if cookiecutter.use_jwt %}
85
+ data.user_id = current_user.id
86
+ {%- endif %}
87
+ return await conversation_service.create_conversation(data)
88
+
89
+
90
+ @router.get("/{conversation_id}", response_model=ConversationReadWithMessages)
91
+ async def get_conversation(
92
+ conversation_id: UUID,
93
+ conversation_service: ConversationSvc,
94
+ {%- if cookiecutter.use_jwt %}
95
+ current_user: CurrentUser,
96
+ {%- endif %}
97
+ ):
98
+ """Get a conversation with all its messages.
99
+
100
+ Raises 404 if the conversation does not exist.
101
+ """
102
+ return await conversation_service.get_conversation(conversation_id, include_messages=True)
103
+
104
+
105
+ @router.patch("/{conversation_id}", response_model=ConversationRead)
106
+ async def update_conversation(
107
+ conversation_id: UUID,
108
+ data: ConversationUpdate,
109
+ conversation_service: ConversationSvc,
110
+ {%- if cookiecutter.use_jwt %}
111
+ current_user: CurrentUser,
112
+ {%- endif %}
113
+ ):
114
+ """Update a conversation's title or archived status.
115
+
116
+ Raises 404 if the conversation does not exist.
117
+ """
118
+ return await conversation_service.update_conversation(conversation_id, data)
119
+
120
+
121
+ @router.delete("/{conversation_id}", status_code=status.HTTP_204_NO_CONTENT)
122
+ async def delete_conversation(
123
+ conversation_id: UUID,
124
+ conversation_service: ConversationSvc,
125
+ {%- if cookiecutter.use_jwt %}
126
+ current_user: CurrentUser,
127
+ {%- endif %}
128
+ ):
129
+ """Delete a conversation and all its messages.
130
+
131
+ Raises 404 if the conversation does not exist.
132
+ """
133
+ await conversation_service.delete_conversation(conversation_id)
134
+
135
+
136
+ @router.post(
137
+ "/{conversation_id}/archive",
138
+ response_model=ConversationRead,
139
+ )
140
+ async def archive_conversation(
141
+ conversation_id: UUID,
142
+ conversation_service: ConversationSvc,
143
+ {%- if cookiecutter.use_jwt %}
144
+ current_user: CurrentUser,
145
+ {%- endif %}
146
+ ):
147
+ """Archive a conversation.
148
+
149
+ Archived conversations are hidden from the default list view.
150
+ """
151
+ return await conversation_service.archive_conversation(conversation_id)
152
+
153
+
154
+ @router.get("/{conversation_id}/messages", response_model=list[MessageReadSimple])
155
+ async def list_messages(
156
+ conversation_id: UUID,
157
+ conversation_service: ConversationSvc,
158
+ {%- if cookiecutter.use_jwt %}
159
+ current_user: CurrentUser,
160
+ {%- endif %}
161
+ skip: int = Query(0, ge=0),
162
+ limit: int = Query(100, ge=1, le=500),
163
+ ):
164
+ """List messages in a conversation.
165
+
166
+ Returns messages ordered by creation time (oldest first).
167
+ """
168
+ return await conversation_service.list_messages(conversation_id, skip=skip, limit=limit)
169
+
170
+
171
+ @router.post(
172
+ "/{conversation_id}/messages",
173
+ response_model=MessageRead,
174
+ status_code=status.HTTP_201_CREATED,
175
+ )
176
+ async def add_message(
177
+ conversation_id: UUID,
178
+ data: MessageCreate,
179
+ conversation_service: ConversationSvc,
180
+ {%- if cookiecutter.use_jwt %}
181
+ current_user: CurrentUser,
182
+ {%- endif %}
183
+ ):
184
+ """Add a message to a conversation.
185
+
186
+ Raises 404 if the conversation does not exist.
187
+ """
188
+ return await conversation_service.add_message(conversation_id, data)
189
+
190
+
191
+ {%- elif cookiecutter.use_sqlite %}
192
+
193
+
194
+ @router.get("", response_model=list[ConversationRead])
195
+ def list_conversations(
196
+ conversation_service: ConversationSvc,
197
+ {%- if cookiecutter.use_jwt %}
198
+ current_user: CurrentUser,
199
+ {%- endif %}
200
+ skip: int = Query(0, ge=0, description="Number of conversations to skip"),
201
+ limit: int = Query(50, ge=1, le=100, description="Maximum conversations to return"),
202
+ include_archived: bool = Query(False, description="Include archived conversations"),
203
+ ):
204
+ """List conversations for the current user.
205
+
206
+ Returns conversations ordered by most recently updated.
207
+ """
208
+ return conversation_service.list_conversations(
209
+ {%- if cookiecutter.use_jwt %}
210
+ user_id=str(current_user.id),
211
+ {%- endif %}
212
+ skip=skip,
213
+ limit=limit,
214
+ include_archived=include_archived,
215
+ )
216
+
217
+
218
+ @router.post("", response_model=ConversationRead, status_code=status.HTTP_201_CREATED)
219
+ def create_conversation(
220
+ conversation_service: ConversationSvc,
221
+ {%- if cookiecutter.use_jwt %}
222
+ current_user: CurrentUser,
223
+ {%- endif %}
224
+ data: ConversationCreate | None = None,
225
+ ):
226
+ """Create a new conversation.
227
+
228
+ The title is optional and can be set later.
229
+ """
230
+ if data is None:
231
+ data = ConversationCreate()
232
+ {%- if cookiecutter.use_jwt %}
233
+ data.user_id = str(current_user.id)
234
+ {%- endif %}
235
+ return conversation_service.create_conversation(data)
236
+
237
+
238
+ @router.get("/{conversation_id}", response_model=ConversationReadWithMessages)
239
+ def get_conversation(
240
+ conversation_id: str,
241
+ conversation_service: ConversationSvc,
242
+ {%- if cookiecutter.use_jwt %}
243
+ current_user: CurrentUser,
244
+ {%- endif %}
245
+ ):
246
+ """Get a conversation with all its messages.
247
+
248
+ Raises 404 if the conversation does not exist.
249
+ """
250
+ return conversation_service.get_conversation(conversation_id, include_messages=True)
251
+
252
+
253
+ @router.patch("/{conversation_id}", response_model=ConversationRead)
254
+ def update_conversation(
255
+ conversation_id: str,
256
+ data: ConversationUpdate,
257
+ conversation_service: ConversationSvc,
258
+ {%- if cookiecutter.use_jwt %}
259
+ current_user: CurrentUser,
260
+ {%- endif %}
261
+ ):
262
+ """Update a conversation's title or archived status.
263
+
264
+ Raises 404 if the conversation does not exist.
265
+ """
266
+ return conversation_service.update_conversation(conversation_id, data)
267
+
268
+
269
+ @router.delete("/{conversation_id}", status_code=status.HTTP_204_NO_CONTENT)
270
+ def delete_conversation(
271
+ conversation_id: str,
272
+ conversation_service: ConversationSvc,
273
+ {%- if cookiecutter.use_jwt %}
274
+ current_user: CurrentUser,
275
+ {%- endif %}
276
+ ):
277
+ """Delete a conversation and all its messages.
278
+
279
+ Raises 404 if the conversation does not exist.
280
+ """
281
+ conversation_service.delete_conversation(conversation_id)
282
+
283
+
284
+ @router.post(
285
+ "/{conversation_id}/archive",
286
+ response_model=ConversationRead,
287
+ )
288
+ def archive_conversation(
289
+ conversation_id: str,
290
+ conversation_service: ConversationSvc,
291
+ {%- if cookiecutter.use_jwt %}
292
+ current_user: CurrentUser,
293
+ {%- endif %}
294
+ ):
295
+ """Archive a conversation.
296
+
297
+ Archived conversations are hidden from the default list view.
298
+ """
299
+ return conversation_service.archive_conversation(conversation_id)
300
+
301
+
302
+ @router.get("/{conversation_id}/messages", response_model=list[MessageReadSimple])
303
+ def list_messages(
304
+ conversation_id: str,
305
+ conversation_service: ConversationSvc,
306
+ {%- if cookiecutter.use_jwt %}
307
+ current_user: CurrentUser,
308
+ {%- endif %}
309
+ skip: int = Query(0, ge=0),
310
+ limit: int = Query(100, ge=1, le=500),
311
+ ):
312
+ """List messages in a conversation.
313
+
314
+ Returns messages ordered by creation time (oldest first).
315
+ """
316
+ return conversation_service.list_messages(conversation_id, skip=skip, limit=limit)
317
+
318
+
319
+ @router.post(
320
+ "/{conversation_id}/messages",
321
+ response_model=MessageRead,
322
+ status_code=status.HTTP_201_CREATED,
323
+ )
324
+ def add_message(
325
+ conversation_id: str,
326
+ data: MessageCreate,
327
+ conversation_service: ConversationSvc,
328
+ {%- if cookiecutter.use_jwt %}
329
+ current_user: CurrentUser,
330
+ {%- endif %}
331
+ ):
332
+ """Add a message to a conversation.
333
+
334
+ Raises 404 if the conversation does not exist.
335
+ """
336
+ return conversation_service.add_message(conversation_id, data)
337
+
338
+
339
+ {%- elif cookiecutter.use_mongodb %}
340
+
341
+
342
+ @router.get("", response_model=list[ConversationRead])
343
+ async def list_conversations(
344
+ conversation_service: ConversationSvc,
345
+ {%- if cookiecutter.use_jwt %}
346
+ current_user: CurrentUser,
347
+ {%- endif %}
348
+ skip: int = Query(0, ge=0, description="Number of conversations to skip"),
349
+ limit: int = Query(50, ge=1, le=100, description="Maximum conversations to return"),
350
+ include_archived: bool = Query(False, description="Include archived conversations"),
351
+ ):
352
+ """List conversations for the current user.
353
+
354
+ Returns conversations ordered by most recently updated.
355
+ """
356
+ return await conversation_service.list_conversations(
357
+ {%- if cookiecutter.use_jwt %}
358
+ user_id=str(current_user.id),
359
+ {%- endif %}
360
+ skip=skip,
361
+ limit=limit,
362
+ include_archived=include_archived,
363
+ )
364
+
365
+
366
+ @router.post("", response_model=ConversationRead, status_code=status.HTTP_201_CREATED)
367
+ async def create_conversation(
368
+ conversation_service: ConversationSvc,
369
+ {%- if cookiecutter.use_jwt %}
370
+ current_user: CurrentUser,
371
+ {%- endif %}
372
+ data: ConversationCreate | None = None,
373
+ ):
374
+ """Create a new conversation.
375
+
376
+ The title is optional and can be set later.
377
+ """
378
+ if data is None:
379
+ data = ConversationCreate()
380
+ {%- if cookiecutter.use_jwt %}
381
+ data.user_id = str(current_user.id)
382
+ {%- endif %}
383
+ return await conversation_service.create_conversation(data)
384
+
385
+
386
+ @router.get("/{conversation_id}", response_model=ConversationReadWithMessages)
387
+ async def get_conversation(
388
+ conversation_id: str,
389
+ conversation_service: ConversationSvc,
390
+ {%- if cookiecutter.use_jwt %}
391
+ current_user: CurrentUser,
392
+ {%- endif %}
393
+ ):
394
+ """Get a conversation with all its messages.
395
+
396
+ Raises 404 if the conversation does not exist.
397
+ """
398
+ return await conversation_service.get_conversation(conversation_id, include_messages=True)
399
+
400
+
401
+ @router.patch("/{conversation_id}", response_model=ConversationRead)
402
+ async def update_conversation(
403
+ conversation_id: str,
404
+ data: ConversationUpdate,
405
+ conversation_service: ConversationSvc,
406
+ {%- if cookiecutter.use_jwt %}
407
+ current_user: CurrentUser,
408
+ {%- endif %}
409
+ ):
410
+ """Update a conversation's title or archived status.
411
+
412
+ Raises 404 if the conversation does not exist.
413
+ """
414
+ return await conversation_service.update_conversation(conversation_id, data)
415
+
416
+
417
+ @router.delete("/{conversation_id}", status_code=status.HTTP_204_NO_CONTENT)
418
+ async def delete_conversation(
419
+ conversation_id: str,
420
+ conversation_service: ConversationSvc,
421
+ {%- if cookiecutter.use_jwt %}
422
+ current_user: CurrentUser,
423
+ {%- endif %}
424
+ ):
425
+ """Delete a conversation and all its messages.
426
+
427
+ Raises 404 if the conversation does not exist.
428
+ """
429
+ await conversation_service.delete_conversation(conversation_id)
430
+
431
+
432
+ @router.post(
433
+ "/{conversation_id}/archive",
434
+ response_model=ConversationRead,
435
+ )
436
+ async def archive_conversation(
437
+ conversation_id: str,
438
+ conversation_service: ConversationSvc,
439
+ {%- if cookiecutter.use_jwt %}
440
+ current_user: CurrentUser,
441
+ {%- endif %}
442
+ ):
443
+ """Archive a conversation.
444
+
445
+ Archived conversations are hidden from the default list view.
446
+ """
447
+ return await conversation_service.archive_conversation(conversation_id)
448
+
449
+
450
+ @router.get("/{conversation_id}/messages", response_model=list[MessageReadSimple])
451
+ async def list_messages(
452
+ conversation_id: str,
453
+ conversation_service: ConversationSvc,
454
+ {%- if cookiecutter.use_jwt %}
455
+ current_user: CurrentUser,
456
+ {%- endif %}
457
+ skip: int = Query(0, ge=0),
458
+ limit: int = Query(100, ge=1, le=500),
459
+ ):
460
+ """List messages in a conversation.
461
+
462
+ Returns messages ordered by creation time (oldest first).
463
+ """
464
+ return await conversation_service.list_messages(conversation_id, skip=skip, limit=limit)
465
+
466
+
467
+ @router.post(
468
+ "/{conversation_id}/messages",
469
+ response_model=MessageRead,
470
+ status_code=status.HTTP_201_CREATED,
471
+ )
472
+ async def add_message(
473
+ conversation_id: str,
474
+ data: MessageCreate,
475
+ conversation_service: ConversationSvc,
476
+ {%- if cookiecutter.use_jwt %}
477
+ current_user: CurrentUser,
478
+ {%- endif %}
479
+ ):
480
+ """Add a message to a conversation.
481
+
482
+ Raises 404 if the conversation does not exist.
483
+ """
484
+ return await conversation_service.add_message(conversation_id, data)
485
+
486
+
487
+ {%- endif %}
488
+ {%- else %}
489
+ """Conversation routes - not configured."""
490
+ {%- endif %}
@@ -0,0 +1,227 @@
1
+ """Health check endpoints.
2
+
3
+ Provides Kubernetes-compatible health check endpoints:
4
+ - /health - Simple liveness check
5
+ - /health/live - Detailed liveness probe
6
+ - /health/ready - Readiness probe with dependency checks
7
+ """
8
+ {%- if cookiecutter.use_database or cookiecutter.enable_redis %}
9
+ # ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
10
+ {%- endif %}
11
+
12
+ from datetime import UTC, datetime
13
+ from typing import Any
14
+
15
+ from fastapi import APIRouter
16
+ from fastapi.responses import JSONResponse
17
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
18
+ from sqlalchemy import text
19
+ {%- endif %}
20
+
21
+ from app.core.config import settings
22
+ {%- if cookiecutter.use_database or cookiecutter.enable_redis %}
23
+ from app.api.deps import {% if cookiecutter.use_database %}DBSession{% endif %}{% if cookiecutter.use_database and cookiecutter.enable_redis %}, {% endif %}{% if cookiecutter.enable_redis %}Redis{% endif %}
24
+
25
+ {%- endif %}
26
+
27
+ router = APIRouter()
28
+
29
+
30
+ def _build_health_response(
31
+ status: str,
32
+ checks: dict[str, Any] | None = None,
33
+ details: dict[str, Any] | None = None,
34
+ ) -> dict[str, Any]:
35
+ """Build a structured health response."""
36
+ response: dict[str, Any] = {
37
+ "status": status,
38
+ "timestamp": datetime.now(UTC).isoformat(),
39
+ "service": settings.PROJECT_NAME,
40
+ }
41
+ if checks is not None:
42
+ response["checks"] = checks
43
+ if details is not None:
44
+ response["details"] = details
45
+ return response
46
+
47
+
48
+ @router.get("/health")
49
+ async def health_check() -> dict[str, str]:
50
+ """Simple liveness probe - check if application is running.
51
+
52
+ This is a lightweight check that should always succeed if the
53
+ application is running. Use this for basic connectivity tests.
54
+
55
+ Returns:
56
+ {"status": "healthy"}
57
+ """
58
+ return {"status": "healthy"}
59
+
60
+
61
+ @router.get("/health/live")
62
+ async def liveness_probe() -> dict[str, Any]:
63
+ """Detailed liveness probe for Kubernetes.
64
+
65
+ This endpoint is designed for Kubernetes liveness probes.
66
+ It checks if the application process is alive and responding.
67
+ Failure indicates the container should be restarted.
68
+
69
+ Returns:
70
+ Structured response with timestamp and service info.
71
+ """
72
+ return _build_health_response(
73
+ status="alive",
74
+ details={
75
+ "version": getattr(settings, "VERSION", "1.0.0"),
76
+ "environment": settings.ENVIRONMENT,
77
+ },
78
+ )
79
+
80
+
81
+ @router.get("/health/ready", response_model=None)
82
+ async def readiness_probe(
83
+ {%- if cookiecutter.use_database %}
84
+ db: DBSession,
85
+ {%- endif %}
86
+ {%- if cookiecutter.enable_redis %}
87
+ redis: Redis,
88
+ {%- endif %}
89
+ ) -> dict[str, Any] | JSONResponse:
90
+ """Readiness probe for Kubernetes.
91
+
92
+ This endpoint checks if all dependencies are ready to handle traffic.
93
+ It verifies database connections, Redis, and other critical services.
94
+ Failure indicates traffic should be temporarily diverted.
95
+
96
+ Checks performed:
97
+ {%- if cookiecutter.use_database %}
98
+ - Database connectivity
99
+ {%- endif %}
100
+ {%- if cookiecutter.enable_redis %}
101
+ - Redis connectivity
102
+ {%- endif %}
103
+
104
+ Returns:
105
+ Structured response with individual check results.
106
+ Returns 503 if any critical check fails.
107
+ """
108
+ checks: dict[str, dict[str, Any]] = {}
109
+
110
+ {%- if cookiecutter.use_postgresql %}
111
+ # Database check
112
+ try:
113
+ start = datetime.now(UTC)
114
+ await db.execute(text("SELECT 1"))
115
+ latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
116
+ checks["database"] = {
117
+ "status": "healthy",
118
+ "latency_ms": round(latency_ms, 2),
119
+ "type": "postgresql",
120
+ }
121
+ except Exception as e:
122
+ checks["database"] = {
123
+ "status": "unhealthy",
124
+ "error": str(e),
125
+ "type": "postgresql",
126
+ }
127
+ {%- endif %}
128
+
129
+ {%- if cookiecutter.use_mongodb %}
130
+ # Database check
131
+ try:
132
+ start = datetime.now(UTC)
133
+ await db.command("ping")
134
+ latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
135
+ checks["database"] = {
136
+ "status": "healthy",
137
+ "latency_ms": round(latency_ms, 2),
138
+ "type": "mongodb",
139
+ }
140
+ except Exception as e:
141
+ checks["database"] = {
142
+ "status": "unhealthy",
143
+ "error": str(e),
144
+ "type": "mongodb",
145
+ }
146
+ {%- endif %}
147
+
148
+ {%- if cookiecutter.use_sqlite %}
149
+ # Database check
150
+ try:
151
+ start = datetime.now(UTC)
152
+ db.execute(text("SELECT 1"))
153
+ latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
154
+ checks["database"] = {
155
+ "status": "healthy",
156
+ "latency_ms": round(latency_ms, 2),
157
+ "type": "sqlite",
158
+ }
159
+ except Exception as e:
160
+ checks["database"] = {
161
+ "status": "unhealthy",
162
+ "error": str(e),
163
+ "type": "sqlite",
164
+ }
165
+ {%- endif %}
166
+
167
+ {%- if cookiecutter.enable_redis %}
168
+ # Redis check
169
+ try:
170
+ start = datetime.now(UTC)
171
+ is_healthy = await redis.ping()
172
+ latency_ms = (datetime.now(UTC) - start).total_seconds() * 1000
173
+ if is_healthy:
174
+ checks["redis"] = {
175
+ "status": "healthy",
176
+ "latency_ms": round(latency_ms, 2),
177
+ }
178
+ else:
179
+ checks["redis"] = {
180
+ "status": "unhealthy",
181
+ "error": "Ping failed",
182
+ }
183
+ except Exception as e:
184
+ checks["redis"] = {
185
+ "status": "unhealthy",
186
+ "error": str(e),
187
+ }
188
+ {%- endif %}
189
+
190
+ # Determine overall health
191
+ all_healthy = all(
192
+ check.get("status") == "healthy" for check in checks.values()
193
+ ) if checks else True
194
+
195
+ response_data = _build_health_response(
196
+ status="ready" if all_healthy else "not_ready",
197
+ checks=checks,
198
+ )
199
+
200
+ if not all_healthy:
201
+ return JSONResponse(status_code=503, content=response_data)
202
+
203
+ return response_data
204
+
205
+
206
+ # Backward compatibility - keep /ready endpoint
207
+ @router.get("/ready", response_model=None)
208
+ async def readiness_check(
209
+ {%- if cookiecutter.use_database %}
210
+ db: DBSession,
211
+ {%- endif %}
212
+ {%- if cookiecutter.enable_redis %}
213
+ redis: Redis,
214
+ {%- endif %}
215
+ ) -> dict[str, Any] | JSONResponse:
216
+ """Readiness check (alias for /health/ready).
217
+
218
+ Deprecated: Use /health/ready instead.
219
+ """
220
+ return await readiness_probe(
221
+ {%- if cookiecutter.use_database %}
222
+ db=db,
223
+ {%- endif %}
224
+ {%- if cookiecutter.enable_redis %}
225
+ redis=redis,
226
+ {%- endif %}
227
+ )