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