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,106 @@
1
+ {%- if cookiecutter.use_celery %}
2
+ """Example Celery tasks."""
3
+
4
+ import logging
5
+ import time
6
+ from typing import Any
7
+
8
+ from celery import shared_task
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @shared_task(bind=True, max_retries=3)
14
+ def example_task(self, message: str) -> dict[str, Any]:
15
+ """
16
+ Example task that processes a message.
17
+
18
+ Args:
19
+ message: Message to process
20
+
21
+ Returns:
22
+ Result dictionary with processed message
23
+ """
24
+ logger.info(f"Processing message: {message}")
25
+
26
+ try:
27
+ # Simulate some work
28
+ time.sleep(1)
29
+
30
+ result = {
31
+ "status": "completed",
32
+ "message": f"Processed: {message}",
33
+ "task_id": self.request.id,
34
+ }
35
+ logger.info(f"Task completed: {result}")
36
+ return result
37
+
38
+ except Exception as exc:
39
+ logger.error(f"Task failed: {exc}")
40
+ # Retry with exponential backoff
41
+ raise self.retry(exc=exc, countdown=2 ** self.request.retries) from exc
42
+
43
+
44
+ @shared_task(bind=True)
45
+ def long_running_task(self, duration: int = 10) -> dict[str, Any]:
46
+ """
47
+ Example long-running task with progress updates.
48
+
49
+ Args:
50
+ duration: Duration in seconds
51
+
52
+ Returns:
53
+ Result dictionary
54
+ """
55
+ logger.info(f"Starting long-running task for {duration} seconds")
56
+
57
+ for i in range(duration):
58
+ time.sleep(1)
59
+ # Update task state with progress
60
+ self.update_state(
61
+ state="PROGRESS",
62
+ meta={"current": i + 1, "total": duration}
63
+ )
64
+ logger.info(f"Progress: {i + 1}/{duration}")
65
+
66
+ return {
67
+ "status": "completed",
68
+ "duration": duration,
69
+ "task_id": self.request.id,
70
+ }
71
+
72
+
73
+ @shared_task
74
+ def send_email_task(to: str, subject: str, body: str) -> dict[str, Any]:
75
+ """
76
+ Example email sending task.
77
+
78
+ Replace with actual email sending logic (e.g., using smtp, sendgrid, etc.)
79
+
80
+ Args:
81
+ to: Recipient email
82
+ subject: Email subject
83
+ body: Email body
84
+
85
+ Returns:
86
+ Result dictionary
87
+ """
88
+ logger.info(f"Sending email to {to}: {subject}")
89
+
90
+ # TODO: Implement actual email sending
91
+ # Example with SMTP:
92
+ # import smtplib
93
+ # from email.mime.text import MIMEText
94
+ # ...
95
+
96
+ # Simulate sending
97
+ time.sleep(0.5)
98
+
99
+ return {
100
+ "status": "sent",
101
+ "to": to,
102
+ "subject": subject,
103
+ }
104
+ {%- else %}
105
+ # Celery not enabled for this project
106
+ {%- endif %}
@@ -0,0 +1,29 @@
1
+ {%- if cookiecutter.use_taskiq %}
2
+ """Taskiq scheduled tasks (cron-like)."""
3
+
4
+ from app.worker.taskiq_app import broker
5
+ from app.worker.tasks.taskiq_examples import example_task
6
+
7
+
8
+ # Define scheduled tasks using labels
9
+ # These are picked up by the scheduler
10
+
11
+ @broker.task(schedule=[{"cron": "* * * * *"}]) # Every minute
12
+ async def scheduled_example() -> dict:
13
+ """Example scheduled task that runs every minute."""
14
+ result = await example_task.kiq("scheduled")
15
+ return {"scheduled": True, "task_id": str(result.task_id)}
16
+
17
+
18
+ # Alternative: Define schedules in scheduler source
19
+ # The scheduler will read these when started with --source flag
20
+ SCHEDULES = [
21
+ {
22
+ "task": "app.worker.tasks.taskiq_examples:example_task",
23
+ "cron": "*/5 * * * *", # Every 5 minutes
24
+ "args": ["periodic-5min"],
25
+ },
26
+ ]
27
+ {%- else %}
28
+ # Taskiq not enabled for this project
29
+ {%- endif %}
@@ -0,0 +1,92 @@
1
+ {%- if cookiecutter.use_taskiq %}
2
+ """Example Taskiq tasks."""
3
+
4
+ import asyncio
5
+ import logging
6
+ from typing import Any
7
+
8
+ from app.worker.taskiq_app import broker
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @broker.task
14
+ async def example_task(message: str) -> dict[str, Any]:
15
+ """
16
+ Example async task that processes a message.
17
+
18
+ Args:
19
+ message: Message to process
20
+
21
+ Returns:
22
+ Result dictionary with processed message
23
+ """
24
+ logger.info(f"Processing message: {message}")
25
+
26
+ # Simulate async work
27
+ await asyncio.sleep(1)
28
+
29
+ result = {
30
+ "status": "completed",
31
+ "message": f"Processed: {message}",
32
+ }
33
+ logger.info(f"Task completed: {result}")
34
+ return result
35
+
36
+
37
+ @broker.task
38
+ async def long_running_task(duration: int = 10) -> dict[str, Any]:
39
+ """
40
+ Example long-running async task.
41
+
42
+ Args:
43
+ duration: Duration in seconds
44
+
45
+ Returns:
46
+ Result dictionary
47
+ """
48
+ logger.info(f"Starting long-running task for {duration} seconds")
49
+
50
+ for i in range(duration):
51
+ await asyncio.sleep(1)
52
+ logger.info(f"Progress: {i + 1}/{duration}")
53
+
54
+ return {
55
+ "status": "completed",
56
+ "duration": duration,
57
+ }
58
+
59
+
60
+ @broker.task
61
+ async def send_email_task(to: str, subject: str, body: str) -> dict[str, Any]:
62
+ """
63
+ Example email sending task.
64
+
65
+ Replace with actual email sending logic (e.g., using aiosmtplib, sendgrid, etc.)
66
+
67
+ Args:
68
+ to: Recipient email
69
+ subject: Email subject
70
+ body: Email body
71
+
72
+ Returns:
73
+ Result dictionary
74
+ """
75
+ logger.info(f"Sending email to {to}: {subject}")
76
+
77
+ # TODO: Implement actual email sending
78
+ # Example with aiosmtplib:
79
+ # import aiosmtplib
80
+ # ...
81
+
82
+ # Simulate sending
83
+ await asyncio.sleep(0.5)
84
+
85
+ return {
86
+ "status": "sent",
87
+ "to": to,
88
+ "subject": subject,
89
+ }
90
+ {%- else %}
91
+ # Taskiq not enabled for this project
92
+ {%- endif %}
@@ -0,0 +1 @@
1
+ """Project CLI module."""
@@ -0,0 +1,438 @@
1
+ """Project management CLI."""
2
+ # ruff: noqa: E402 - Import at bottom to avoid circular imports
3
+
4
+ import click
5
+ from tabulate import tabulate
6
+
7
+
8
+ @click.group()
9
+ @click.version_option(version="0.1.0", prog_name="{{ cookiecutter.project_slug }}")
10
+ def cli():
11
+ """{{ cookiecutter.project_name }} management CLI."""
12
+ pass
13
+
14
+
15
+ # === Server Commands ===
16
+ @cli.group("server")
17
+ def server_cli():
18
+ """Server commands."""
19
+ pass
20
+
21
+
22
+ @server_cli.command("run")
23
+ @click.option("--host", default="0.0.0.0", help="Host to bind to")
24
+ @click.option("--port", default=8000, type=int, help="Port to bind to")
25
+ @click.option("--reload", is_flag=True, help="Enable auto-reload")
26
+ def server_run(host: str, port: int, reload: bool):
27
+ """Run the development server."""
28
+ import uvicorn
29
+ uvicorn.run(
30
+ "app.main:app",
31
+ host=host,
32
+ port=port,
33
+ reload=reload,
34
+ )
35
+
36
+
37
+ @server_cli.command("routes")
38
+ def server_routes():
39
+ """Show all registered routes."""
40
+ from app.main import app
41
+
42
+ routes = []
43
+ for route in app.routes:
44
+ if hasattr(route, "methods"):
45
+ for method in route.methods - {"HEAD", "OPTIONS"}:
46
+ routes.append([method, route.path, getattr(route, "name", "-")])
47
+
48
+ click.echo(tabulate(routes, headers=["Method", "Path", "Name"]))
49
+
50
+
51
+ {%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite %}
52
+
53
+
54
+ # === Database Commands ===
55
+ @cli.group("db")
56
+ def db_cli():
57
+ """Database commands."""
58
+ pass
59
+
60
+
61
+ @db_cli.command("init")
62
+ def db_init():
63
+ """Initialize the database (run all migrations)."""
64
+ from alembic import command
65
+ from alembic.config import Config
66
+
67
+ click.echo("Initializing database...")
68
+ alembic_cfg = Config("alembic.ini")
69
+ command.upgrade(alembic_cfg, "head")
70
+ click.secho("Database initialized.", fg="green")
71
+
72
+
73
+ @db_cli.command("migrate")
74
+ @click.option("-m", "--message", required=True, help="Migration message")
75
+ def db_migrate(message: str):
76
+ """Create a new migration."""
77
+ from alembic import command
78
+ from alembic.config import Config
79
+
80
+ alembic_cfg = Config("alembic.ini")
81
+ command.revision(alembic_cfg, message=message, autogenerate=True)
82
+ click.secho(f"Migration created: {message}", fg="green")
83
+
84
+
85
+ @db_cli.command("upgrade")
86
+ @click.option("--revision", default="head", help="Revision to upgrade to")
87
+ def db_upgrade(revision: str):
88
+ """Run database migrations."""
89
+ from alembic import command
90
+ from alembic.config import Config
91
+
92
+ alembic_cfg = Config("alembic.ini")
93
+ command.upgrade(alembic_cfg, revision)
94
+ click.secho(f"Upgraded to: {revision}", fg="green")
95
+
96
+
97
+ @db_cli.command("downgrade")
98
+ @click.option("--revision", default="-1", help="Revision to downgrade to")
99
+ def db_downgrade(revision: str):
100
+ """Rollback database migrations."""
101
+ from alembic import command
102
+ from alembic.config import Config
103
+
104
+ alembic_cfg = Config("alembic.ini")
105
+ command.downgrade(alembic_cfg, revision)
106
+ click.secho(f"Downgraded to: {revision}", fg="green")
107
+
108
+
109
+ @db_cli.command("current")
110
+ def db_current():
111
+ """Show current migration revision."""
112
+ from alembic import command
113
+ from alembic.config import Config
114
+
115
+ alembic_cfg = Config("alembic.ini")
116
+ command.current(alembic_cfg)
117
+
118
+
119
+ @db_cli.command("history")
120
+ def db_history():
121
+ """Show migration history."""
122
+ from alembic import command
123
+ from alembic.config import Config
124
+
125
+ alembic_cfg = Config("alembic.ini")
126
+ command.history(alembic_cfg)
127
+ {%- endif %}
128
+
129
+
130
+ {%- if cookiecutter.use_celery %}
131
+
132
+
133
+ # === Celery Commands ===
134
+ @cli.group("celery")
135
+ def celery_cli():
136
+ """Celery worker commands."""
137
+ pass
138
+
139
+
140
+ @celery_cli.command("worker")
141
+ @click.option("--loglevel", default="info", help="Log level (debug, info, warning, error)")
142
+ @click.option("--concurrency", default=4, type=int, help="Number of concurrent workers")
143
+ def celery_worker(loglevel: str, concurrency: int):
144
+ """Start Celery worker."""
145
+ import subprocess
146
+ subprocess.run([
147
+ "celery", "-A", "app.worker.celery_app", "worker",
148
+ f"--loglevel={loglevel}",
149
+ f"--concurrency={concurrency}",
150
+ ])
151
+
152
+
153
+ @celery_cli.command("beat")
154
+ @click.option("--loglevel", default="info", help="Log level (debug, info, warning, error)")
155
+ def celery_beat(loglevel: str):
156
+ """Start Celery beat scheduler."""
157
+ import subprocess
158
+ subprocess.run([
159
+ "celery", "-A", "app.worker.celery_app", "beat",
160
+ f"--loglevel={loglevel}",
161
+ ])
162
+
163
+
164
+ @celery_cli.command("flower")
165
+ @click.option("--port", default=5555, type=int, help="Flower web UI port")
166
+ def celery_flower(port: int):
167
+ """Start Flower monitoring UI."""
168
+ import subprocess
169
+ subprocess.run([
170
+ "celery", "-A", "app.worker.celery_app", "flower",
171
+ f"--port={port}",
172
+ ])
173
+ {%- endif %}
174
+
175
+ {%- if cookiecutter.use_taskiq %}
176
+
177
+
178
+ # === Taskiq Commands ===
179
+ @cli.group("taskiq")
180
+ def taskiq_cli():
181
+ """Taskiq worker commands."""
182
+ pass
183
+
184
+
185
+ @taskiq_cli.command("worker")
186
+ @click.option("--workers", default=2, type=int, help="Number of workers")
187
+ @click.option("--reload", is_flag=True, help="Enable auto-reload for development")
188
+ def taskiq_worker(workers: int, reload: bool):
189
+ """Start Taskiq worker."""
190
+ import subprocess
191
+ cmd = [
192
+ "taskiq", "worker", "app.worker.taskiq_app:broker",
193
+ f"--workers={workers}",
194
+ ]
195
+ if reload:
196
+ cmd.append("--reload")
197
+ subprocess.run(cmd)
198
+
199
+
200
+ @taskiq_cli.command("scheduler")
201
+ def taskiq_scheduler():
202
+ """Start Taskiq scheduler for periodic tasks."""
203
+ import subprocess
204
+ subprocess.run([
205
+ "taskiq", "scheduler", "app.worker.taskiq_app:scheduler",
206
+ ])
207
+ {%- endif %}
208
+
209
+ {%- if cookiecutter.use_jwt %}
210
+
211
+
212
+ # === User Commands ===
213
+ @cli.group("user")
214
+ def user_cli():
215
+ """User management commands."""
216
+ pass
217
+
218
+
219
+ @user_cli.command("create")
220
+ @click.option("--email", prompt=True, help="User email")
221
+ @click.option("--password", prompt=True, hide_input=True, confirmation_prompt=True, help="User password")
222
+ @click.option("--role", type=click.Choice(["user", "admin"]), default="user", help="User role")
223
+ @click.option("--superuser", is_flag=True, default=False, help="Create as superuser")
224
+ def user_create(email: str, password: str, role: str, superuser: bool):
225
+ """Create a new user."""
226
+ import asyncio
227
+ from app.core.exceptions import AlreadyExistsError
228
+ from app.db.models.user import UserRole
229
+ from app.schemas.user import UserCreate
230
+ from app.services.user import UserService
231
+ {%- if cookiecutter.use_postgresql %}
232
+ from app.db.session import async_session_maker
233
+
234
+ async def _create():
235
+ async with async_session_maker() as session:
236
+ user_service = UserService(session)
237
+ try:
238
+ user_in = UserCreate(email=email, password=password, role=UserRole(role))
239
+ user = await user_service.register(user_in)
240
+
241
+ if superuser:
242
+ user.is_superuser = True
243
+ session.add(user)
244
+
245
+ await session.commit()
246
+ return user
247
+ except AlreadyExistsError:
248
+ click.secho(f"User already exists: {email}", fg="red")
249
+ return None
250
+ {%- elif cookiecutter.use_sqlite %}
251
+ from app.db.session import SessionLocal
252
+
253
+ def _create():
254
+ with SessionLocal() as session:
255
+ user_service = UserService(session)
256
+ try:
257
+ user_in = UserCreate(email=email, password=password, role=UserRole(role))
258
+ user = user_service.register(user_in)
259
+
260
+ if superuser:
261
+ user.is_superuser = True
262
+ session.add(user)
263
+
264
+ session.commit()
265
+ return user
266
+ except AlreadyExistsError:
267
+ click.secho(f"User already exists: {email}", fg="red")
268
+ return None
269
+ {%- endif %}
270
+
271
+ {%- if cookiecutter.use_postgresql %}
272
+ user = asyncio.run(_create())
273
+ {%- else %}
274
+ user = _create()
275
+ {%- endif %}
276
+ if user:
277
+ click.secho(f"User created: {user.email} (role: {user.role})", fg="green")
278
+
279
+
280
+ @user_cli.command("create-admin")
281
+ @click.option("--email", prompt=True, help="Admin email")
282
+ @click.option("--password", prompt=True, hide_input=True, confirmation_prompt=True, help="Admin password")
283
+ def user_create_admin(email: str, password: str):
284
+ """Create an admin user.
285
+
286
+ This is a shortcut for creating a user with admin role and superuser privileges.
287
+ Use this to create the initial admin account after setting up the database.
288
+ """
289
+ import asyncio
290
+ from app.core.exceptions import AlreadyExistsError
291
+ from app.db.models.user import UserRole
292
+ from app.schemas.user import UserCreate
293
+ from app.services.user import UserService
294
+ {%- if cookiecutter.use_postgresql %}
295
+ from app.db.session import async_session_maker
296
+
297
+ async def _create():
298
+ async with async_session_maker() as session:
299
+ user_service = UserService(session)
300
+ try:
301
+ user_in = UserCreate(email=email, password=password, role=UserRole.ADMIN)
302
+ user = await user_service.register(user_in)
303
+
304
+ # Admin users are also superusers
305
+ user.is_superuser = True
306
+ session.add(user)
307
+
308
+ await session.commit()
309
+ return user
310
+ except AlreadyExistsError:
311
+ click.secho(f"User already exists: {email}", fg="red")
312
+ return None
313
+ {%- elif cookiecutter.use_sqlite %}
314
+ from app.db.session import SessionLocal
315
+
316
+ def _create():
317
+ with SessionLocal() as session:
318
+ user_service = UserService(session)
319
+ try:
320
+ user_in = UserCreate(email=email, password=password, role=UserRole.ADMIN)
321
+ user = user_service.register(user_in)
322
+
323
+ # Admin users are also superusers
324
+ user.is_superuser = True
325
+ session.add(user)
326
+
327
+ session.commit()
328
+ return user
329
+ except AlreadyExistsError:
330
+ click.secho(f"User already exists: {email}", fg="red")
331
+ return None
332
+ {%- endif %}
333
+
334
+ {%- if cookiecutter.use_postgresql %}
335
+ user = asyncio.run(_create())
336
+ {%- else %}
337
+ user = _create()
338
+ {%- endif %}
339
+ if user:
340
+ click.secho(f"Admin user created: {user.email}", fg="green")
341
+ click.echo("This user has admin role and superuser privileges.")
342
+
343
+
344
+ @user_cli.command("set-role")
345
+ @click.argument("email")
346
+ @click.option("--role", type=click.Choice(["user", "admin"]), required=True, help="New role")
347
+ def user_set_role(email: str, role: str):
348
+ """Change a user's role."""
349
+ import asyncio
350
+ from app.core.exceptions import NotFoundError
351
+ from app.db.models.user import UserRole
352
+ from app.services.user import UserService
353
+ {%- if cookiecutter.use_postgresql %}
354
+ from app.db.session import async_session_maker
355
+
356
+ async def _update():
357
+ async with async_session_maker() as session:
358
+ user_service = UserService(session)
359
+ try:
360
+ user = await user_service.get_by_email(email)
361
+ user.role = UserRole(role).value
362
+ session.add(user)
363
+ await session.commit()
364
+ return user
365
+ except NotFoundError:
366
+ click.secho(f"User not found: {email}", fg="red")
367
+ return None
368
+
369
+ user = asyncio.run(_update())
370
+ {%- elif cookiecutter.use_sqlite %}
371
+ from app.db.session import SessionLocal
372
+
373
+ with SessionLocal() as session:
374
+ user_service = UserService(session)
375
+ try:
376
+ user = user_service.get_by_email(email)
377
+ user.role = UserRole(role).value
378
+ session.add(user)
379
+ session.commit()
380
+ except NotFoundError:
381
+ click.secho(f"User not found: {email}", fg="red")
382
+ user = None
383
+ {%- endif %}
384
+ if user:
385
+ click.secho(f"User {email} role updated to: {role}", fg="green")
386
+
387
+
388
+ @user_cli.command("list")
389
+ def user_list():
390
+ """List all users."""
391
+ import asyncio
392
+ from app.services.user import UserService
393
+ {%- if cookiecutter.use_postgresql %}
394
+ from app.db.session import async_session_maker
395
+
396
+ async def _list():
397
+ async with async_session_maker() as session:
398
+ user_service = UserService(session)
399
+ return await user_service.get_multi()
400
+
401
+ users = asyncio.run(_list())
402
+ {%- elif cookiecutter.use_sqlite %}
403
+ from app.db.session import SessionLocal
404
+
405
+ with SessionLocal() as session:
406
+ user_service = UserService(session)
407
+ users = user_service.get_multi()
408
+ {%- endif %}
409
+
410
+ if not users:
411
+ click.echo("No users found.")
412
+ return
413
+
414
+ table = [[u.id, u.email, u.role, u.is_active, u.is_superuser] for u in users]
415
+ click.echo(tabulate(table, headers=["ID", "Email", "Role", "Active", "Superuser"]))
416
+ {%- endif %}
417
+
418
+
419
+ # === Custom Commands ===
420
+ @cli.group("cmd")
421
+ def cmd_cli():
422
+ """Custom commands."""
423
+ pass
424
+
425
+
426
+ # Register all custom commands from app/commands/
427
+ from app.commands import register_commands
428
+
429
+ register_commands(cmd_cli)
430
+
431
+
432
+ def main():
433
+ """Main entry point."""
434
+ cli()
435
+
436
+
437
+ if __name__ == "__main__":
438
+ main()