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,221 @@
|
|
|
1
|
+
"""API versioning utilities and deprecation handling.
|
|
2
|
+
|
|
3
|
+
This module provides tools for managing API version deprecation:
|
|
4
|
+
- Deprecation middleware for entire API versions
|
|
5
|
+
- Deprecation decorator for individual endpoints
|
|
6
|
+
- RFC 8594 compliant deprecation headers
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from functools import wraps
|
|
12
|
+
|
|
13
|
+
import logfire
|
|
14
|
+
from fastapi import Request, Response
|
|
15
|
+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VersionDeprecationMiddleware(BaseHTTPMiddleware):
|
|
19
|
+
"""Middleware to add deprecation headers for deprecated API versions.
|
|
20
|
+
|
|
21
|
+
Adds RFC 8594 compliant headers:
|
|
22
|
+
- Deprecation: Indicates the version is deprecated
|
|
23
|
+
- Sunset: Indicates when the version will be removed
|
|
24
|
+
- Link: Points to migration documentation
|
|
25
|
+
|
|
26
|
+
Usage in main.py:
|
|
27
|
+
app.add_middleware(
|
|
28
|
+
VersionDeprecationMiddleware,
|
|
29
|
+
deprecated_versions={"v1": {"sunset": "2025-06-01", "link": "/docs/migration/v2"}},
|
|
30
|
+
)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
app,
|
|
36
|
+
deprecated_versions: dict[str, dict] | None = None,
|
|
37
|
+
):
|
|
38
|
+
"""Initialize the middleware.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
app: The ASGI application.
|
|
42
|
+
deprecated_versions: Dict mapping version prefixes to deprecation info.
|
|
43
|
+
Each entry should have:
|
|
44
|
+
- sunset: ISO date string when version will be removed (optional)
|
|
45
|
+
- link: URL to migration documentation (optional)
|
|
46
|
+
- message: Custom deprecation message (optional)
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
{
|
|
50
|
+
"v1": {
|
|
51
|
+
"sunset": "2025-06-01",
|
|
52
|
+
"link": "https://api.example.com/docs/migration/v2",
|
|
53
|
+
"message": "Please migrate to API v2",
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
"""
|
|
57
|
+
super().__init__(app)
|
|
58
|
+
self.deprecated_versions = deprecated_versions or {}
|
|
59
|
+
|
|
60
|
+
async def dispatch(
|
|
61
|
+
self, request: Request, call_next: RequestResponseEndpoint
|
|
62
|
+
) -> Response:
|
|
63
|
+
"""Process the request and add deprecation headers if needed."""
|
|
64
|
+
response = await call_next(request)
|
|
65
|
+
|
|
66
|
+
# Check if request path matches a deprecated version
|
|
67
|
+
path = request.url.path
|
|
68
|
+
for version, info in self.deprecated_versions.items():
|
|
69
|
+
if f"/api/{version}/" in path or path.endswith(f"/api/{version}"):
|
|
70
|
+
self._add_deprecation_headers(response, version, info)
|
|
71
|
+
self._log_deprecated_usage(request, version)
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
return response
|
|
75
|
+
|
|
76
|
+
def _add_deprecation_headers(
|
|
77
|
+
self, response: Response, version: str, info: dict
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Add RFC 8594 deprecation headers to the response."""
|
|
80
|
+
# Deprecation header - indicates the API is deprecated
|
|
81
|
+
response.headers["Deprecation"] = "true"
|
|
82
|
+
|
|
83
|
+
# Sunset header - when the API will be removed
|
|
84
|
+
if sunset := info.get("sunset"):
|
|
85
|
+
# Convert to HTTP date format
|
|
86
|
+
sunset_date = datetime.fromisoformat(sunset)
|
|
87
|
+
response.headers["Sunset"] = sunset_date.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
|
88
|
+
|
|
89
|
+
# Link header - documentation for migration
|
|
90
|
+
if link := info.get("link"):
|
|
91
|
+
response.headers["Link"] = f'<{link}>; rel="deprecation"'
|
|
92
|
+
|
|
93
|
+
# Custom warning header
|
|
94
|
+
message = info.get("message", f"API {version} is deprecated")
|
|
95
|
+
response.headers["X-API-Deprecation-Warning"] = message
|
|
96
|
+
|
|
97
|
+
def _log_deprecated_usage(self, request: Request, version: str) -> None:
|
|
98
|
+
"""Log usage of deprecated API version for monitoring."""
|
|
99
|
+
logfire.warn(
|
|
100
|
+
"Deprecated API version accessed",
|
|
101
|
+
version=version,
|
|
102
|
+
path=request.url.path,
|
|
103
|
+
method=request.method,
|
|
104
|
+
client_ip=request.client.host if request.client else None,
|
|
105
|
+
user_agent=request.headers.get("User-Agent"),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def deprecated(
|
|
110
|
+
sunset: str | None = None,
|
|
111
|
+
message: str | None = None,
|
|
112
|
+
link: str | None = None,
|
|
113
|
+
):
|
|
114
|
+
"""Decorator to mark an endpoint as deprecated.
|
|
115
|
+
|
|
116
|
+
Adds deprecation headers to responses from the decorated endpoint.
|
|
117
|
+
Use this for deprecating individual endpoints within an active API version.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
sunset: ISO date string when endpoint will be removed.
|
|
121
|
+
message: Custom deprecation message.
|
|
122
|
+
link: URL to migration documentation.
|
|
123
|
+
|
|
124
|
+
Usage:
|
|
125
|
+
@router.get("/old-endpoint")
|
|
126
|
+
@deprecated(
|
|
127
|
+
sunset="2025-06-01",
|
|
128
|
+
message="Use /new-endpoint instead",
|
|
129
|
+
link="/docs/migration",
|
|
130
|
+
)
|
|
131
|
+
async def old_endpoint():
|
|
132
|
+
...
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def decorator(func: Callable) -> Callable:
|
|
136
|
+
@wraps(func)
|
|
137
|
+
async def wrapper(*args, **kwargs):
|
|
138
|
+
# Get the response from the endpoint
|
|
139
|
+
result = await func(*args, **kwargs)
|
|
140
|
+
|
|
141
|
+
# Find Response object in args (FastAPI injects it)
|
|
142
|
+
response = None
|
|
143
|
+
for arg in args:
|
|
144
|
+
if isinstance(arg, Response):
|
|
145
|
+
response = arg
|
|
146
|
+
break
|
|
147
|
+
for value in kwargs.values():
|
|
148
|
+
if isinstance(value, Response):
|
|
149
|
+
response = value
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
# If we have a Response object, add headers
|
|
153
|
+
if response:
|
|
154
|
+
response.headers["Deprecation"] = "true"
|
|
155
|
+
if sunset:
|
|
156
|
+
sunset_date = datetime.fromisoformat(sunset)
|
|
157
|
+
response.headers["Sunset"] = sunset_date.strftime(
|
|
158
|
+
"%a, %d %b %Y %H:%M:%S GMT"
|
|
159
|
+
)
|
|
160
|
+
if link:
|
|
161
|
+
response.headers["Link"] = f'<{link}>; rel="deprecation"'
|
|
162
|
+
if message:
|
|
163
|
+
response.headers["X-API-Deprecation-Warning"] = message
|
|
164
|
+
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
# Add deprecation info to OpenAPI schema
|
|
168
|
+
wrapper.__doc__ = (
|
|
169
|
+
f"{func.__doc__ or ''}\n\n"
|
|
170
|
+
f"**DEPRECATED**"
|
|
171
|
+
f"{f': {message}' if message else ''}"
|
|
172
|
+
f"{f' (Sunset: {sunset})' if sunset else ''}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return wrapper
|
|
176
|
+
|
|
177
|
+
return decorator
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Example usage documentation
|
|
181
|
+
"""
|
|
182
|
+
## Adding a New API Version
|
|
183
|
+
|
|
184
|
+
1. Create a new version folder:
|
|
185
|
+
```
|
|
186
|
+
app/api/routes/v2/
|
|
187
|
+
├── __init__.py
|
|
188
|
+
├── health.py
|
|
189
|
+
├── auth.py
|
|
190
|
+
└── ...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
2. Create the v2 router in `v2/__init__.py`:
|
|
194
|
+
```python
|
|
195
|
+
from fastapi import APIRouter
|
|
196
|
+
v2_router = APIRouter()
|
|
197
|
+
# Include routes...
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
3. Add the v2 router in `app/api/router.py`:
|
|
201
|
+
```python
|
|
202
|
+
from app.api.routes.v2 import v2_router
|
|
203
|
+
|
|
204
|
+
api_router.include_router(v1_router, prefix="/v1")
|
|
205
|
+
api_router.include_router(v2_router, prefix="/v2")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
4. Mark v1 as deprecated in `main.py`:
|
|
209
|
+
```python
|
|
210
|
+
app.add_middleware(
|
|
211
|
+
VersionDeprecationMiddleware,
|
|
212
|
+
deprecated_versions={
|
|
213
|
+
"v1": {
|
|
214
|
+
"sunset": "2025-12-31",
|
|
215
|
+
"link": "/docs/migration/v2",
|
|
216
|
+
"message": "Please migrate to API v2",
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
"""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""External service clients.
|
|
2
|
+
|
|
3
|
+
This module contains thin wrappers around external services like Redis.
|
|
4
|
+
"""
|
|
5
|
+
{%- if cookiecutter.enable_redis %}
|
|
6
|
+
|
|
7
|
+
from app.clients.redis import RedisClient
|
|
8
|
+
{%- endif %}
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
{%- if cookiecutter.enable_redis %}
|
|
12
|
+
"RedisClient",
|
|
13
|
+
{%- endif %}
|
|
14
|
+
]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{%- if cookiecutter.enable_redis %}
|
|
2
|
+
"""Redis client wrapper.
|
|
3
|
+
|
|
4
|
+
Provides a class-based Redis client for connection management and operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from redis import asyncio as aioredis
|
|
8
|
+
|
|
9
|
+
from app.core.config import settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RedisClient:
|
|
13
|
+
"""Redis client wrapper for connection lifecycle management.
|
|
14
|
+
|
|
15
|
+
Usage in FastAPI lifespan:
|
|
16
|
+
async with contextmanager():
|
|
17
|
+
redis = RedisClient(settings.REDIS_URL)
|
|
18
|
+
await redis.connect()
|
|
19
|
+
yield {"redis": redis}
|
|
20
|
+
await redis.close()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, url: str | None = None):
|
|
24
|
+
self.url = url or settings.REDIS_URL
|
|
25
|
+
self.client: aioredis.Redis | None = None
|
|
26
|
+
|
|
27
|
+
async def connect(self) -> None:
|
|
28
|
+
"""Connect to Redis server."""
|
|
29
|
+
self.client = aioredis.from_url(
|
|
30
|
+
self.url,
|
|
31
|
+
encoding="utf-8",
|
|
32
|
+
decode_responses=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def close(self) -> None:
|
|
36
|
+
"""Close Redis connection."""
|
|
37
|
+
if self.client:
|
|
38
|
+
await self.client.close()
|
|
39
|
+
self.client = None
|
|
40
|
+
|
|
41
|
+
async def get(self, key: str) -> str | None:
|
|
42
|
+
"""Get a value by key."""
|
|
43
|
+
if not self.client:
|
|
44
|
+
raise RuntimeError("Redis client not connected")
|
|
45
|
+
return await self.client.get(key)
|
|
46
|
+
|
|
47
|
+
async def set(
|
|
48
|
+
self,
|
|
49
|
+
key: str,
|
|
50
|
+
value: str,
|
|
51
|
+
ttl: int | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Set a value with optional TTL (in seconds)."""
|
|
54
|
+
if not self.client:
|
|
55
|
+
raise RuntimeError("Redis client not connected")
|
|
56
|
+
await self.client.set(key, value, ex=ttl)
|
|
57
|
+
|
|
58
|
+
async def delete(self, key: str) -> int:
|
|
59
|
+
"""Delete a key. Returns number of keys deleted."""
|
|
60
|
+
if not self.client:
|
|
61
|
+
raise RuntimeError("Redis client not connected")
|
|
62
|
+
return await self.client.delete(key)
|
|
63
|
+
|
|
64
|
+
async def exists(self, key: str) -> bool:
|
|
65
|
+
"""Check if key exists."""
|
|
66
|
+
if not self.client:
|
|
67
|
+
raise RuntimeError("Redis client not connected")
|
|
68
|
+
return bool(await self.client.exists(key))
|
|
69
|
+
|
|
70
|
+
async def ping(self) -> bool:
|
|
71
|
+
"""Ping Redis server. Returns True if connected."""
|
|
72
|
+
if not self.client:
|
|
73
|
+
return False
|
|
74
|
+
try:
|
|
75
|
+
await self.client.ping()
|
|
76
|
+
return True
|
|
77
|
+
except Exception:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def raw(self) -> aioredis.Redis:
|
|
82
|
+
"""Access the underlying aioredis client for advanced operations."""
|
|
83
|
+
if not self.client:
|
|
84
|
+
raise RuntimeError("Redis client not connected")
|
|
85
|
+
return self.client
|
|
86
|
+
{%- else %}
|
|
87
|
+
"""Redis client - not configured."""
|
|
88
|
+
{%- endif %}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom commands system with auto-discovery.
|
|
3
|
+
|
|
4
|
+
This module provides a Django-like custom commands system for FastAPI + Click.
|
|
5
|
+
Commands are auto-discovered from this package and registered to the CLI.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# In app/commands/my_command.py
|
|
9
|
+
from app.commands import command
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
@command("my-command", help="Description of my command")
|
|
13
|
+
@click.option("--option", "-o", help="Some option")
|
|
14
|
+
def my_command(option: str):
|
|
15
|
+
click.echo(f"Running with {option}")
|
|
16
|
+
|
|
17
|
+
# Then use it:
|
|
18
|
+
# project cmd my-command --option value
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import importlib
|
|
22
|
+
import pkgutil
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import click
|
|
27
|
+
|
|
28
|
+
# Registry for custom commands
|
|
29
|
+
_commands: list[click.Command] = []
|
|
30
|
+
_discovered = False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def command(name: str | None = None, **kwargs) -> Callable:
|
|
34
|
+
"""
|
|
35
|
+
Decorator to register a custom command.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: Command name (defaults to function name with underscores replaced by hyphens)
|
|
39
|
+
**kwargs: Additional arguments passed to click.command()
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
@command("seed", help="Seed database with initial data")
|
|
43
|
+
@click.option("--count", "-c", default=10)
|
|
44
|
+
def seed_data(count: int):
|
|
45
|
+
click.echo(f"Seeding {count} records...")
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def decorator(func: Callable) -> click.Command:
|
|
49
|
+
cmd_name = name or func.__name__.replace("_", "-")
|
|
50
|
+
cmd = click.command(cmd_name, **kwargs)(func)
|
|
51
|
+
_commands.append(cmd)
|
|
52
|
+
return cmd
|
|
53
|
+
|
|
54
|
+
return decorator
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def discover_commands() -> list[click.Command]:
|
|
58
|
+
"""
|
|
59
|
+
Auto-discover all commands in this package.
|
|
60
|
+
|
|
61
|
+
Imports all modules in the app.commands package (except those starting with _)
|
|
62
|
+
which triggers the @command decorator to register them.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of discovered click.Command objects
|
|
66
|
+
"""
|
|
67
|
+
global _discovered
|
|
68
|
+
|
|
69
|
+
if _discovered:
|
|
70
|
+
return _commands
|
|
71
|
+
|
|
72
|
+
package_dir = Path(__file__).parent
|
|
73
|
+
|
|
74
|
+
for _, module_name, _ in pkgutil.iter_modules([str(package_dir)]):
|
|
75
|
+
if module_name.startswith("_"):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
importlib.import_module(f"app.commands.{module_name}")
|
|
80
|
+
except ImportError as e:
|
|
81
|
+
click.secho(f"Warning: Failed to import command module '{module_name}': {e}", fg="yellow")
|
|
82
|
+
|
|
83
|
+
_discovered = True
|
|
84
|
+
return _commands
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def register_commands(cli: click.Group) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Register all discovered commands to a CLI group.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
cli: The click.Group to add commands to
|
|
93
|
+
"""
|
|
94
|
+
commands = discover_commands()
|
|
95
|
+
|
|
96
|
+
for cmd in commands:
|
|
97
|
+
cli.add_command(cmd)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def success(message: str) -> None:
|
|
101
|
+
"""Print success message in green."""
|
|
102
|
+
click.secho(message, fg="green")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def error(message: str) -> None:
|
|
106
|
+
"""Print error message in red."""
|
|
107
|
+
click.secho(message, fg="red")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def warning(message: str) -> None:
|
|
111
|
+
"""Print warning message in yellow."""
|
|
112
|
+
click.secho(message, fg="yellow")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def info(message: str) -> None:
|
|
116
|
+
"""Print info message."""
|
|
117
|
+
click.echo(message)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{%- if cookiecutter.use_postgresql or cookiecutter.use_sqlite -%}
|
|
2
|
+
"""
|
|
3
|
+
Cleanup old or stale data from the database.
|
|
4
|
+
|
|
5
|
+
This command is useful for maintenance tasks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from app.commands import command, info, success, warning
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@command("cleanup", help="Clean up old data from the database")
|
|
17
|
+
@click.option("--days", "-d", default=30, type=int, help="Delete records older than N days")
|
|
18
|
+
@click.option("--dry-run", is_flag=True, help="Show what would be deleted without making changes")
|
|
19
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation prompt")
|
|
20
|
+
def cleanup(days: int, dry_run: bool, force: bool) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Remove old records from the database.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
project cmd cleanup --days 90
|
|
26
|
+
project cmd cleanup --days 30 --dry-run
|
|
27
|
+
project cmd cleanup --days 7 --force
|
|
28
|
+
"""
|
|
29
|
+
cutoff_date = datetime.utcnow() - timedelta(days=days)
|
|
30
|
+
|
|
31
|
+
if dry_run:
|
|
32
|
+
info(f"[DRY RUN] Would delete records older than {cutoff_date}")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if not force and not click.confirm(f"Delete all records older than {days} days ({cutoff_date})?"):
|
|
36
|
+
warning("Aborted.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
{%- if cookiecutter.use_postgresql %}
|
|
40
|
+
from app.db.session import async_session_maker
|
|
41
|
+
|
|
42
|
+
async def _cleanup():
|
|
43
|
+
async with async_session_maker() as _session:
|
|
44
|
+
info(f"Cleaning up records older than {cutoff_date}...")
|
|
45
|
+
|
|
46
|
+
# Add your cleanup logic here
|
|
47
|
+
# Example:
|
|
48
|
+
# result = await session.execute(
|
|
49
|
+
# delete(YourModel).where(YourModel.created_at < cutoff_date)
|
|
50
|
+
# )
|
|
51
|
+
# await session.commit()
|
|
52
|
+
# deleted_count = result.rowcount
|
|
53
|
+
|
|
54
|
+
deleted_count = 0 # Replace with actual count
|
|
55
|
+
success(f"Deleted {deleted_count} records.")
|
|
56
|
+
|
|
57
|
+
asyncio.run(_cleanup())
|
|
58
|
+
{%- elif cookiecutter.use_sqlite %}
|
|
59
|
+
from app.db.session import SessionLocal
|
|
60
|
+
|
|
61
|
+
with SessionLocal() as _session:
|
|
62
|
+
info(f"Cleaning up records older than {cutoff_date}...")
|
|
63
|
+
|
|
64
|
+
# Add your cleanup logic here
|
|
65
|
+
# Example:
|
|
66
|
+
# result = session.execute(
|
|
67
|
+
# delete(YourModel).where(YourModel.created_at < cutoff_date)
|
|
68
|
+
# )
|
|
69
|
+
# session.commit()
|
|
70
|
+
# deleted_count = result.rowcount
|
|
71
|
+
|
|
72
|
+
deleted_count = 0 # Replace with actual count
|
|
73
|
+
success(f"Deleted {deleted_count} records.")
|
|
74
|
+
{%- endif %}
|
|
75
|
+
{%- endif %}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example custom command.
|
|
3
|
+
|
|
4
|
+
This is a template showing how to create custom CLI commands.
|
|
5
|
+
Copy this file and modify it to create your own commands.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from app.commands import command, info, success
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@command("hello", help="Example command that greets the user")
|
|
14
|
+
@click.option("--name", "-n", default="World", help="Name to greet")
|
|
15
|
+
@click.option("--count", "-c", default=1, type=int, help="Number of greetings")
|
|
16
|
+
def hello(name: str, count: int) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Greet someone multiple times.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
project cmd hello --name Alice --count 3
|
|
22
|
+
"""
|
|
23
|
+
info(f"Greeting {name} {count} time(s)...")
|
|
24
|
+
|
|
25
|
+
for i in range(count):
|
|
26
|
+
click.echo(f" [{i + 1}] Hello, {name}!")
|
|
27
|
+
|
|
28
|
+
success("Done!")
|