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,322 @@
1
+ {%- if cookiecutter.use_jwt and cookiecutter.use_postgresql %}
2
+ """User repository (PostgreSQL async).
3
+
4
+ Contains only database operations. Business logic (password hashing,
5
+ validation) is handled by UserService in app/services/user.py.
6
+ """
7
+
8
+ from uuid import UUID
9
+
10
+ from sqlalchemy import select
11
+ from sqlalchemy.ext.asyncio import AsyncSession
12
+
13
+ from app.db.models.user import User
14
+
15
+
16
+ async def get_by_id(db: AsyncSession, user_id: UUID) -> User | None:
17
+ """Get user by ID."""
18
+ return await db.get(User, user_id)
19
+
20
+
21
+ async def get_by_email(db: AsyncSession, email: str) -> User | None:
22
+ """Get user by email."""
23
+ result = await db.execute(select(User).where(User.email == email))
24
+ return result.scalar_one_or_none()
25
+
26
+
27
+ {%- if cookiecutter.enable_oauth %}
28
+
29
+
30
+ async def get_by_oauth(db: AsyncSession, provider: str, oauth_id: str) -> User | None:
31
+ """Get user by OAuth provider and ID."""
32
+ result = await db.execute(
33
+ select(User).where(User.oauth_provider == provider, User.oauth_id == oauth_id)
34
+ )
35
+ return result.scalar_one_or_none()
36
+ {%- endif %}
37
+
38
+
39
+ async def get_multi(
40
+ db: AsyncSession,
41
+ *,
42
+ skip: int = 0,
43
+ limit: int = 100,
44
+ ) -> list[User]:
45
+ """Get multiple users with pagination."""
46
+ result = await db.execute(select(User).offset(skip).limit(limit))
47
+ return list(result.scalars().all())
48
+
49
+
50
+ async def create(
51
+ db: AsyncSession,
52
+ *,
53
+ email: str,
54
+ hashed_password: str | None,
55
+ full_name: str | None = None,
56
+ is_active: bool = True,
57
+ is_superuser: bool = False,
58
+ role: str = "user",
59
+ {%- if cookiecutter.enable_oauth %}
60
+ oauth_provider: str | None = None,
61
+ oauth_id: str | None = None,
62
+ {%- endif %}
63
+ ) -> User:
64
+ """Create a new user.
65
+
66
+ Note: Password should already be hashed by the service layer.
67
+ """
68
+ user = User(
69
+ email=email,
70
+ hashed_password=hashed_password,
71
+ full_name=full_name,
72
+ is_active=is_active,
73
+ is_superuser=is_superuser,
74
+ role=role,
75
+ {%- if cookiecutter.enable_oauth %}
76
+ oauth_provider=oauth_provider,
77
+ oauth_id=oauth_id,
78
+ {%- endif %}
79
+ )
80
+ db.add(user)
81
+ await db.flush()
82
+ await db.refresh(user)
83
+ return user
84
+
85
+
86
+ async def update(
87
+ db: AsyncSession,
88
+ *,
89
+ db_user: User,
90
+ update_data: dict,
91
+ ) -> User:
92
+ """Update a user.
93
+
94
+ Note: If password needs updating, it should already be hashed.
95
+ """
96
+ for field, value in update_data.items():
97
+ setattr(db_user, field, value)
98
+
99
+ db.add(db_user)
100
+ await db.flush()
101
+ await db.refresh(db_user)
102
+ return db_user
103
+
104
+
105
+ async def delete(db: AsyncSession, user_id: UUID) -> User | None:
106
+ """Delete a user."""
107
+ user = await get_by_id(db, user_id)
108
+ if user:
109
+ await db.delete(user)
110
+ await db.flush()
111
+ return user
112
+
113
+
114
+ {%- elif cookiecutter.use_jwt and cookiecutter.use_sqlite %}
115
+ """User repository (SQLite sync).
116
+
117
+ Contains only database operations. Business logic (password hashing,
118
+ validation) is handled by UserService in app/services/user.py.
119
+ """
120
+
121
+ from sqlalchemy import select
122
+ from sqlalchemy.orm import Session
123
+
124
+ from app.db.models.user import User
125
+
126
+
127
+ def get_by_id(db: Session, user_id: str) -> User | None:
128
+ """Get user by ID."""
129
+ return db.get(User, user_id)
130
+
131
+
132
+ def get_by_email(db: Session, email: str) -> User | None:
133
+ """Get user by email."""
134
+ result = db.execute(select(User).where(User.email == email))
135
+ return result.scalar_one_or_none()
136
+
137
+
138
+ {%- if cookiecutter.enable_oauth %}
139
+
140
+
141
+ def get_by_oauth(db: Session, provider: str, oauth_id: str) -> User | None:
142
+ """Get user by OAuth provider and ID."""
143
+ result = db.execute(
144
+ select(User).where(User.oauth_provider == provider, User.oauth_id == oauth_id)
145
+ )
146
+ return result.scalar_one_or_none()
147
+ {%- endif %}
148
+
149
+
150
+ def get_multi(
151
+ db: Session,
152
+ *,
153
+ skip: int = 0,
154
+ limit: int = 100,
155
+ ) -> list[User]:
156
+ """Get multiple users with pagination."""
157
+ result = db.execute(select(User).offset(skip).limit(limit))
158
+ return list(result.scalars().all())
159
+
160
+
161
+ def create(
162
+ db: Session,
163
+ *,
164
+ email: str,
165
+ hashed_password: str | None,
166
+ full_name: str | None = None,
167
+ is_active: bool = True,
168
+ is_superuser: bool = False,
169
+ role: str = "user",
170
+ {%- if cookiecutter.enable_oauth %}
171
+ oauth_provider: str | None = None,
172
+ oauth_id: str | None = None,
173
+ {%- endif %}
174
+ ) -> User:
175
+ """Create a new user.
176
+
177
+ Note: Password should already be hashed by the service layer.
178
+ """
179
+ user = User(
180
+ email=email,
181
+ hashed_password=hashed_password,
182
+ full_name=full_name,
183
+ is_active=is_active,
184
+ is_superuser=is_superuser,
185
+ role=role,
186
+ {%- if cookiecutter.enable_oauth %}
187
+ oauth_provider=oauth_provider,
188
+ oauth_id=oauth_id,
189
+ {%- endif %}
190
+ )
191
+ db.add(user)
192
+ db.flush()
193
+ db.refresh(user)
194
+ return user
195
+
196
+
197
+ def update(
198
+ db: Session,
199
+ *,
200
+ db_user: User,
201
+ update_data: dict,
202
+ ) -> User:
203
+ """Update a user.
204
+
205
+ Note: If password needs updating, it should already be hashed.
206
+ """
207
+ for field, value in update_data.items():
208
+ setattr(db_user, field, value)
209
+
210
+ db.add(db_user)
211
+ db.flush()
212
+ db.refresh(db_user)
213
+ return db_user
214
+
215
+
216
+ def delete(db: Session, user_id: str) -> User | None:
217
+ """Delete a user."""
218
+ user = get_by_id(db, user_id)
219
+ if user:
220
+ db.delete(user)
221
+ db.flush()
222
+ return user
223
+
224
+
225
+ {%- elif cookiecutter.use_jwt and cookiecutter.use_mongodb %}
226
+ """User repository (MongoDB).
227
+
228
+ Contains only database operations. Business logic (password hashing,
229
+ validation) is handled by UserService in app/services/user.py.
230
+ """
231
+
232
+ from app.db.models.user import User
233
+
234
+
235
+ async def get_by_id(user_id: str) -> User | None:
236
+ """Get user by ID."""
237
+ return await User.get(user_id)
238
+
239
+
240
+ async def get_by_email(email: str) -> User | None:
241
+ """Get user by email."""
242
+ return await User.find_one(User.email == email)
243
+
244
+
245
+ {%- if cookiecutter.enable_oauth %}
246
+
247
+
248
+ async def get_by_oauth(provider: str, oauth_id: str) -> User | None:
249
+ """Get user by OAuth provider and ID."""
250
+ return await User.find_one(User.oauth_provider == provider, User.oauth_id == oauth_id)
251
+ {%- endif %}
252
+
253
+
254
+ async def get_multi(
255
+ *,
256
+ skip: int = 0,
257
+ limit: int = 100,
258
+ ) -> list[User]:
259
+ """Get multiple users with pagination."""
260
+ return await User.find_all().skip(skip).limit(limit).to_list()
261
+
262
+
263
+ async def create(
264
+ *,
265
+ email: str,
266
+ hashed_password: str | None,
267
+ full_name: str | None = None,
268
+ is_active: bool = True,
269
+ is_superuser: bool = False,
270
+ role: str = "user",
271
+ {%- if cookiecutter.enable_oauth %}
272
+ oauth_provider: str | None = None,
273
+ oauth_id: str | None = None,
274
+ {%- endif %}
275
+ ) -> User:
276
+ """Create a new user.
277
+
278
+ Note: Password should already be hashed by the service layer.
279
+ """
280
+ user = User(
281
+ email=email,
282
+ hashed_password=hashed_password,
283
+ full_name=full_name,
284
+ is_active=is_active,
285
+ is_superuser=is_superuser,
286
+ role=role,
287
+ {%- if cookiecutter.enable_oauth %}
288
+ oauth_provider=oauth_provider,
289
+ oauth_id=oauth_id,
290
+ {%- endif %}
291
+ )
292
+ await user.insert()
293
+ return user
294
+
295
+
296
+ async def update(
297
+ *,
298
+ db_user: User,
299
+ update_data: dict,
300
+ ) -> User:
301
+ """Update a user.
302
+
303
+ Note: If password needs updating, it should already be hashed.
304
+ """
305
+ for field, value in update_data.items():
306
+ setattr(db_user, field, value)
307
+
308
+ await db_user.save()
309
+ return db_user
310
+
311
+
312
+ async def delete(user_id: str) -> User | None:
313
+ """Delete a user."""
314
+ user = await get_by_id(user_id)
315
+ if user:
316
+ await user.delete()
317
+ return user
318
+
319
+
320
+ {%- else %}
321
+ """User repository - not configured."""
322
+ {%- endif %}
@@ -0,0 +1,358 @@
1
+ {%- if cookiecutter.enable_webhooks and cookiecutter.use_database %}
2
+ {%- if cookiecutter.use_postgresql %}
3
+ """Webhook repository (PostgreSQL async)."""
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlalchemy import func, select
8
+ from sqlalchemy.ext.asyncio import AsyncSession
9
+
10
+ from app.db.models.webhook import Webhook, WebhookDelivery
11
+ from app.schemas.webhook import WebhookUpdate
12
+
13
+
14
+ async def get_by_id(db: AsyncSession, webhook_id: UUID) -> Webhook | None:
15
+ """Get webhook by ID."""
16
+ return await db.get(Webhook, webhook_id)
17
+
18
+
19
+ async def get_list(
20
+ db: AsyncSession,
21
+ *,
22
+ user_id: UUID | None = None,
23
+ skip: int = 0,
24
+ limit: int = 50,
25
+ ) -> tuple[list[Webhook], int]:
26
+ """Get list of webhooks with pagination."""
27
+ query = select(Webhook)
28
+ if user_id:
29
+ query = query.where(Webhook.user_id == user_id)
30
+ query = query.order_by(Webhook.created_at.desc())
31
+
32
+ # Get total count
33
+ count_query = select(func.count()).select_from(query.subquery())
34
+ total = await db.scalar(count_query) or 0
35
+
36
+ # Get paginated results
37
+ query = query.offset(skip).limit(limit)
38
+ result = await db.execute(query)
39
+ return list(result.scalars().all()), total
40
+
41
+
42
+ async def get_by_event(db: AsyncSession, event_type: str) -> list[Webhook]:
43
+ """Get all active webhooks subscribed to an event type."""
44
+ result = await db.execute(
45
+ select(Webhook).where(
46
+ Webhook.is_active.is_(True),
47
+ Webhook.events.contains([event_type]),
48
+ )
49
+ )
50
+ return list(result.scalars().all())
51
+
52
+
53
+ async def create(
54
+ db: AsyncSession,
55
+ *,
56
+ name: str,
57
+ url: str,
58
+ secret: str,
59
+ events: list[str],
60
+ description: str | None = None,
61
+ user_id: UUID | None = None,
62
+ ) -> Webhook:
63
+ """Create a new webhook."""
64
+ webhook = Webhook(
65
+ name=name,
66
+ url=url,
67
+ secret=secret,
68
+ events=events,
69
+ description=description,
70
+ {%- if cookiecutter.use_jwt %}
71
+ user_id=user_id,
72
+ {%- endif %}
73
+ )
74
+ db.add(webhook)
75
+ await db.flush()
76
+ await db.refresh(webhook)
77
+ return webhook
78
+
79
+
80
+ async def update(
81
+ db: AsyncSession,
82
+ webhook: Webhook,
83
+ data: WebhookUpdate,
84
+ ) -> Webhook:
85
+ """Update a webhook."""
86
+ update_data = data.model_dump(exclude_unset=True)
87
+ for field, value in update_data.items():
88
+ setattr(webhook, field, value)
89
+ db.add(webhook)
90
+ await db.flush()
91
+ await db.refresh(webhook)
92
+ return webhook
93
+
94
+
95
+ async def update_secret(db: AsyncSession, webhook: Webhook, new_secret: str) -> Webhook:
96
+ """Update webhook secret."""
97
+ webhook.secret = new_secret
98
+ db.add(webhook)
99
+ await db.flush()
100
+ await db.refresh(webhook)
101
+ return webhook
102
+
103
+
104
+ async def delete(db: AsyncSession, webhook: Webhook) -> None:
105
+ """Delete a webhook."""
106
+ await db.delete(webhook)
107
+ await db.flush()
108
+
109
+
110
+ async def get_deliveries(
111
+ db: AsyncSession,
112
+ webhook_id: UUID,
113
+ *,
114
+ skip: int = 0,
115
+ limit: int = 50,
116
+ ) -> tuple[list[WebhookDelivery], int]:
117
+ """Get delivery history for a webhook."""
118
+ query = (
119
+ select(WebhookDelivery)
120
+ .where(WebhookDelivery.webhook_id == webhook_id)
121
+ .order_by(WebhookDelivery.created_at.desc())
122
+ )
123
+
124
+ count_query = select(func.count()).select_from(query.subquery())
125
+ total = await db.scalar(count_query) or 0
126
+
127
+ query = query.offset(skip).limit(limit)
128
+ result = await db.execute(query)
129
+ return list(result.scalars().all()), total
130
+
131
+
132
+ {%- elif cookiecutter.use_sqlite %}
133
+ """Webhook repository (SQLite sync)."""
134
+
135
+ from sqlalchemy import func, select
136
+ from sqlalchemy.orm import Session as DBSession
137
+
138
+ from app.db.models.webhook import Webhook, WebhookDelivery
139
+ from app.schemas.webhook import WebhookUpdate
140
+
141
+
142
+ def get_by_id(db: DBSession, webhook_id: str) -> Webhook | None:
143
+ """Get webhook by ID."""
144
+ return db.get(Webhook, webhook_id)
145
+
146
+
147
+ def get_list(
148
+ db: DBSession,
149
+ *,
150
+ user_id: str | None = None,
151
+ skip: int = 0,
152
+ limit: int = 50,
153
+ ) -> tuple[list[Webhook], int]:
154
+ """Get list of webhooks with pagination."""
155
+ query = select(Webhook)
156
+ if user_id:
157
+ query = query.where(Webhook.user_id == user_id)
158
+ query = query.order_by(Webhook.created_at.desc())
159
+
160
+ count_query = select(func.count()).select_from(query.subquery())
161
+ total = db.scalar(count_query) or 0
162
+
163
+ query = query.offset(skip).limit(limit)
164
+ result = db.execute(query)
165
+ return list(result.scalars().all()), total
166
+
167
+
168
+ def get_by_event(db: DBSession, event_type: str) -> list[Webhook]:
169
+ """Get all active webhooks subscribed to an event type."""
170
+ # For SQLite, we need to check if event is in the JSON array
171
+ result = db.execute(select(Webhook).where(Webhook.is_active.is_(True)))
172
+ webhooks = result.scalars().all()
173
+ return [w for w in webhooks if event_type in w.events]
174
+
175
+
176
+ def create(
177
+ db: DBSession,
178
+ *,
179
+ name: str,
180
+ url: str,
181
+ secret: str,
182
+ events: list[str],
183
+ description: str | None = None,
184
+ user_id: str | None = None,
185
+ ) -> Webhook:
186
+ """Create a new webhook."""
187
+ webhook = Webhook(
188
+ name=name,
189
+ url=url,
190
+ secret=secret,
191
+ description=description,
192
+ {%- if cookiecutter.use_jwt %}
193
+ user_id=user_id,
194
+ {%- endif %}
195
+ )
196
+ webhook.events = events # Use the property setter
197
+ db.add(webhook)
198
+ db.flush()
199
+ db.refresh(webhook)
200
+ return webhook
201
+
202
+
203
+ def update(
204
+ db: DBSession,
205
+ webhook: Webhook,
206
+ data: WebhookUpdate,
207
+ ) -> Webhook:
208
+ """Update a webhook."""
209
+ update_data = data.model_dump(exclude_unset=True)
210
+ for field, value in update_data.items():
211
+ if field == "events":
212
+ webhook.events = value # Use property setter
213
+ else:
214
+ setattr(webhook, field, value)
215
+ db.add(webhook)
216
+ db.flush()
217
+ db.refresh(webhook)
218
+ return webhook
219
+
220
+
221
+ def update_secret(db: DBSession, webhook: Webhook, new_secret: str) -> Webhook:
222
+ """Update webhook secret."""
223
+ webhook.secret = new_secret
224
+ db.add(webhook)
225
+ db.flush()
226
+ db.refresh(webhook)
227
+ return webhook
228
+
229
+
230
+ def delete(db: DBSession, webhook: Webhook) -> None:
231
+ """Delete a webhook."""
232
+ db.delete(webhook)
233
+ db.flush()
234
+
235
+
236
+ def get_deliveries(
237
+ db: DBSession,
238
+ webhook_id: str,
239
+ *,
240
+ skip: int = 0,
241
+ limit: int = 50,
242
+ ) -> tuple[list[WebhookDelivery], int]:
243
+ """Get delivery history for a webhook."""
244
+ query = (
245
+ select(WebhookDelivery)
246
+ .where(WebhookDelivery.webhook_id == webhook_id)
247
+ .order_by(WebhookDelivery.created_at.desc())
248
+ )
249
+
250
+ count_query = select(func.count()).select_from(query.subquery())
251
+ total = db.scalar(count_query) or 0
252
+
253
+ query = query.offset(skip).limit(limit)
254
+ result = db.execute(query)
255
+ return list(result.scalars().all()), total
256
+
257
+
258
+ {%- elif cookiecutter.use_mongodb %}
259
+ """Webhook repository (MongoDB)."""
260
+
261
+ from app.db.models.webhook import Webhook, WebhookDelivery
262
+ from app.schemas.webhook import WebhookUpdate
263
+
264
+
265
+ async def get_by_id(webhook_id: str) -> Webhook | None:
266
+ """Get webhook by ID."""
267
+ return await Webhook.get(webhook_id)
268
+
269
+
270
+ async def get_list(
271
+ *,
272
+ user_id: str | None = None,
273
+ skip: int = 0,
274
+ limit: int = 50,
275
+ ) -> tuple[list[Webhook], int]:
276
+ """Get list of webhooks with pagination."""
277
+ query = Webhook.find()
278
+ if user_id:
279
+ query = query.find(Webhook.user_id == user_id)
280
+
281
+ total = await query.count()
282
+ webhooks = await query.sort(-Webhook.created_at).skip(skip).limit(limit).to_list()
283
+ return webhooks, total
284
+
285
+
286
+ async def get_by_event(event_type: str) -> list[Webhook]:
287
+ """Get all active webhooks subscribed to an event type."""
288
+ return await Webhook.find(
289
+ Webhook.is_active == True,
290
+ Webhook.events == event_type, # MongoDB $elemMatch
291
+ ).to_list()
292
+
293
+
294
+ async def create(
295
+ *,
296
+ name: str,
297
+ url: str,
298
+ secret: str,
299
+ events: list[str],
300
+ description: str | None = None,
301
+ user_id: str | None = None,
302
+ ) -> Webhook:
303
+ """Create a new webhook."""
304
+ webhook = Webhook(
305
+ name=name,
306
+ url=url,
307
+ secret=secret,
308
+ events=events,
309
+ description=description,
310
+ {%- if cookiecutter.use_jwt %}
311
+ user_id=user_id,
312
+ {%- endif %}
313
+ )
314
+ await webhook.insert()
315
+ return webhook
316
+
317
+
318
+ async def update(
319
+ webhook: Webhook,
320
+ data: WebhookUpdate,
321
+ ) -> Webhook:
322
+ """Update a webhook."""
323
+ update_data = data.model_dump(exclude_unset=True)
324
+ for field, value in update_data.items():
325
+ setattr(webhook, field, value)
326
+ await webhook.save()
327
+ return webhook
328
+
329
+
330
+ async def update_secret(webhook: Webhook, new_secret: str) -> Webhook:
331
+ """Update webhook secret."""
332
+ webhook.secret = new_secret
333
+ await webhook.save()
334
+ return webhook
335
+
336
+
337
+ async def delete(webhook: Webhook) -> None:
338
+ """Delete a webhook."""
339
+ await webhook.delete()
340
+
341
+
342
+ async def get_deliveries(
343
+ webhook_id: str,
344
+ *,
345
+ skip: int = 0,
346
+ limit: int = 50,
347
+ ) -> tuple[list[WebhookDelivery], int]:
348
+ """Get delivery history for a webhook."""
349
+ query = WebhookDelivery.find(WebhookDelivery.webhook_id == webhook_id)
350
+ total = await query.count()
351
+ deliveries = await query.sort(-WebhookDelivery.created_at).skip(skip).limit(limit).to_list()
352
+ return deliveries, total
353
+
354
+
355
+ {%- endif %}
356
+ {%- else %}
357
+ """Webhook repository - not configured."""
358
+ {%- endif %}