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.
- fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
- fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
- fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +442 -0
- fastapi_gen/config.py +356 -0
- fastapi_gen/generator.py +207 -0
- fastapi_gen/prompts.py +874 -0
- fastapi_gen/template/VARIABLES.md +276 -0
- fastapi_gen/template/cookiecutter.json +93 -0
- fastapi_gen/template/hooks/post_gen_project.py +355 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- 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 %}
|
fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py
ADDED
|
@@ -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()
|