@cachly-dev/mcp-server 0.10.48 → 0.10.49
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.
- package/.github/workflows/eas-build-android.yml +58 -0
- package/.github/workflows/eas-update.yml +55 -0
- package/README.md +5 -33
- package/STRATEGY.md +209 -0
- package/apps/travel-chaos-organizer/.env.example +49 -0
- package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
- package/apps/travel-chaos-organizer/Makefile +57 -0
- package/apps/travel-chaos-organizer/README.md +159 -0
- package/apps/travel-chaos-organizer/backend/.env.example +10 -0
- package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
- package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
- package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
- package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
- package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
- package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
- package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
- package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
- package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
- package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
- package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
- package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
- package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
- package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
- package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
- package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
- package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
- package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
- package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
- package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
- package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
- package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
- package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
- package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
- package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
- package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
- package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
- package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
- package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
- package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
- package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
- package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
- package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
- package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
- package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
- package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
- package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
- package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
- package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
- package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
- package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
- package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
- package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
- package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
- package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
- package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
- package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
- package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
- package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
- package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
- package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
- package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
- package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
- package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
- package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
- package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
- package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
- package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
- package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
- package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
- package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
- package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
- package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
- package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
- package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
- package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
- package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
- package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
- package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
- package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
- package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
- package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
- package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
- package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
- package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
- package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
- package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
- package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
- package/apps/travel-chaos-organizer/frontend/package-lock.json +13037 -0
- package/apps/travel-chaos-organizer/frontend/package.json +44 -0
- package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
- package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
- package/apps/travel-chaos-organizer/landing/index.html +773 -0
- package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
- package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
- package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
- package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
- package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
- package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
- package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
- package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
- package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
- package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
- package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
- package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
- package/dist/packages/telegram-notify/client.d.ts +19 -0
- package/dist/packages/telegram-notify/client.js +75 -0
- package/dist/packages/telegram-notify/client.js.map +1 -0
- package/dist/src/__tests__/auth.test.js.map +1 -0
- package/dist/{__tests__ → src/__tests__}/brain-flow.test.js +141 -0
- package/dist/src/__tests__/brain-flow.test.js.map +1 -0
- package/dist/src/__tests__/confidence.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
- package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
- package/dist/src/__tests__/e2e/config.js.map +1 -0
- package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
- package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
- package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
- package/dist/{__tests__ → src/__tests__}/handlers-unit.test.js +0 -2
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/ckg.js.map +1 -0
- package/dist/src/confidence.js.map +1 -0
- package/dist/src/embeddings.js.map +1 -0
- package/dist/src/handlers/advanced.js.map +1 -0
- package/dist/{handlers → src/handlers}/brain.js +134 -30
- package/dist/src/handlers/brain.js.map +1 -0
- package/dist/src/handlers/cache.js.map +1 -0
- package/dist/{handlers → src/handlers}/context.js +9 -4
- package/dist/src/handlers/context.js.map +1 -0
- package/dist/{handlers → src/handlers}/fedbrain.d.ts +6 -0
- package/dist/{handlers → src/handlers}/fedbrain.js +19 -7
- package/dist/src/handlers/fedbrain.js.map +1 -0
- package/dist/src/handlers/instances.js.map +1 -0
- package/dist/{handlers → src/handlers}/roadmap.js +7 -3
- package/dist/src/handlers/roadmap.js.map +1 -0
- package/dist/src/handlers/syndicate.js.map +1 -0
- package/dist/src/handlers/tco.d.ts +16 -0
- package/dist/src/handlers/tco.js +303 -0
- package/dist/src/handlers/tco.js.map +1 -0
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js → src/index.js} +153 -218
- package/dist/src/index.js.map +1 -0
- package/dist/src/namespace.js.map +1 -0
- package/dist/src/notifier.d.ts +5 -0
- package/dist/src/notifier.js +6 -0
- package/dist/src/notifier.js.map +1 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/search.test.js.map +1 -0
- package/dist/{tools.d.ts → src/tools.d.ts} +196 -0
- package/dist/{tools.js → src/tools.js} +178 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +3 -3
- package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
- package/packages/telegram-notify/client.ts +88 -0
- package/packages/telegram-notify/emojis.json +22 -0
- package/packages/telegram-notify/pyproject.toml +17 -0
- package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
- package/packages/telegram-notify/telegram_notify/client.py +87 -0
- package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
- package/scripts/generate-icons.js +28 -0
- package/smithery.yaml +21 -8
- package/src/__tests__/brain-flow.test.ts +174 -0
- package/src/__tests__/handlers-unit.test.ts +1 -2
- package/src/handlers/brain.ts +138 -31
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +21 -7
- package/src/handlers/roadmap.ts +7 -3
- package/src/handlers/tco.ts +347 -0
- package/src/index.ts +139 -220
- package/src/notifier.ts +5 -0
- package/src/tools.ts +191 -0
- package/tsconfig.json +2 -3
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/brain-flow.test.js.map +0 -1
- package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
- package/dist/__tests__/brain-predict-failures.test.js +0 -339
- package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
- package/dist/__tests__/confidence.test.js.map +0 -1
- package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
- package/dist/__tests__/e2e/api-health.test.js.map +0 -1
- package/dist/__tests__/e2e/cli.test.js.map +0 -1
- package/dist/__tests__/e2e/config.js.map +0 -1
- package/dist/__tests__/e2e/funnel.test.js.map +0 -1
- package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
- package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
- package/dist/__tests__/handlers-unit.test.js.map +0 -1
- package/dist/__tests__/namespace.test.js.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/ckg.js.map +0 -1
- package/dist/confidence.js.map +0 -1
- package/dist/embeddings.js.map +0 -1
- package/dist/handlers/advanced.js.map +0 -1
- package/dist/handlers/brain.js.map +0 -1
- package/dist/handlers/cache.js.map +0 -1
- package/dist/handlers/context.js.map +0 -1
- package/dist/handlers/fedbrain.js.map +0 -1
- package/dist/handlers/instances.js.map +0 -1
- package/dist/handlers/roadmap.js.map +0 -1
- package/dist/handlers/syndicate.js.map +0 -1
- package/dist/handlers/team.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/namespace.js.map +0 -1
- package/dist/search.js.map +0 -1
- package/dist/search.test.js.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/__tests__/brain-predict-failures.test.ts +0 -430
- /package/dist/{__tests__ → src/__tests__}/auth.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/auth.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/brain-flow.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/confidence.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/confidence.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/config.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/config.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/handlers-unit.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/namespace.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/namespace.test.js +0 -0
- /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
- /package/dist/{auth.js → src/auth.js} +0 -0
- /package/dist/{ckg.d.ts → src/ckg.d.ts} +0 -0
- /package/dist/{ckg.js → src/ckg.js} +0 -0
- /package/dist/{confidence.d.ts → src/confidence.d.ts} +0 -0
- /package/dist/{confidence.js → src/confidence.js} +0 -0
- /package/dist/{embeddings.d.ts → src/embeddings.d.ts} +0 -0
- /package/dist/{embeddings.js → src/embeddings.js} +0 -0
- /package/dist/{handlers → src/handlers}/advanced.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/advanced.js +0 -0
- /package/dist/{handlers → src/handlers}/brain.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/cache.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/cache.js +0 -0
- /package/dist/{handlers → src/handlers}/context.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/instances.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/instances.js +0 -0
- /package/dist/{handlers → src/handlers}/roadmap.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/syndicate.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/syndicate.js +0 -0
- /package/dist/{handlers → src/handlers}/team.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/team.js +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{namespace.d.ts → src/namespace.d.ts} +0 -0
- /package/dist/{namespace.js → src/namespace.js} +0 -0
- /package/dist/{search.d.ts → src/search.d.ts} +0 -0
- /package/dist/{search.js → src/search.js} +0 -0
- /package/dist/{search.test.d.ts → src/search.test.d.ts} +0 -0
- /package/dist/{search.test.js → src/search.test.js} +0 -0
- /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
- /package/dist/{utils.js → src/utils.js} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""add telegram_chat_id to users
|
|
2
|
+
|
|
3
|
+
Revision ID: 0002
|
|
4
|
+
Revises: 0001
|
|
5
|
+
Create Date: 2025-01-02 00:00:00.000000
|
|
6
|
+
"""
|
|
7
|
+
from typing import Sequence, Union
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
|
|
11
|
+
revision: str = '0002'
|
|
12
|
+
down_revision: Union[str, None] = '0001'
|
|
13
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
14
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def upgrade() -> None:
|
|
18
|
+
op.add_column('users', sa.Column('telegram_chat_id', sa.Text(), nullable=True))
|
|
19
|
+
op.create_index('ix_users_telegram_chat_id', 'users', ['telegram_chat_id'], unique=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def downgrade() -> None:
|
|
23
|
+
op.drop_index('ix_users_telegram_chat_id', 'users')
|
|
24
|
+
op.drop_column('users', 'telegram_chat_id')
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = alembic
|
|
3
|
+
prepend_sys_path = .
|
|
4
|
+
version_path_separator = os
|
|
5
|
+
sqlalchemy.url = postgresql+asyncpg://tco:tco@localhost:5432/tco
|
|
6
|
+
|
|
7
|
+
[loggers]
|
|
8
|
+
keys = root,sqlalchemy,alembic
|
|
9
|
+
|
|
10
|
+
[handlers]
|
|
11
|
+
keys = console
|
|
12
|
+
|
|
13
|
+
[formatters]
|
|
14
|
+
keys = generic
|
|
15
|
+
|
|
16
|
+
[logger_root]
|
|
17
|
+
level = WARN
|
|
18
|
+
handlers = console
|
|
19
|
+
qualname =
|
|
20
|
+
|
|
21
|
+
[logger_sqlalchemy]
|
|
22
|
+
level = WARN
|
|
23
|
+
handlers =
|
|
24
|
+
qualname = sqlalchemy.engine
|
|
25
|
+
|
|
26
|
+
[logger_alembic]
|
|
27
|
+
level = INFO
|
|
28
|
+
handlers =
|
|
29
|
+
qualname = alembic
|
|
30
|
+
|
|
31
|
+
[handler_console]
|
|
32
|
+
class = StreamHandler
|
|
33
|
+
args = (sys.stderr,)
|
|
34
|
+
level = NOTSET
|
|
35
|
+
formatter = generic
|
|
36
|
+
|
|
37
|
+
[formatter_generic]
|
|
38
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
39
|
+
datefmt = %H:%M:%S
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
import httpx
|
|
4
|
+
from fastapi import Depends, HTTPException, Request, status
|
|
5
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
6
|
+
from jose import JWTError, jwt
|
|
7
|
+
from app.config import get_settings
|
|
8
|
+
from app.db.database import get_db
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
|
+
|
|
11
|
+
settings = get_settings()
|
|
12
|
+
bearer_scheme = HTTPBearer()
|
|
13
|
+
|
|
14
|
+
_jwks_cache: dict | None = None
|
|
15
|
+
_jwks_cached_at: datetime | None = None
|
|
16
|
+
_JWKS_TTL = timedelta(hours=1) # refresh every hour to pick up key rotations
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def _get_jwks() -> dict:
|
|
20
|
+
global _jwks_cache, _jwks_cached_at
|
|
21
|
+
now = datetime.now(timezone.utc)
|
|
22
|
+
if _jwks_cache and _jwks_cached_at and (now - _jwks_cached_at) < _JWKS_TTL:
|
|
23
|
+
return _jwks_cache
|
|
24
|
+
url = f"{settings.keycloak_url}/realms/{settings.keycloak_realm}/protocol/openid-connect/certs"
|
|
25
|
+
async with httpx.AsyncClient() as client:
|
|
26
|
+
resp = await client.get(url, timeout=10)
|
|
27
|
+
resp.raise_for_status()
|
|
28
|
+
_jwks_cache = resp.json()
|
|
29
|
+
_jwks_cached_at = now
|
|
30
|
+
return _jwks_cache
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def get_current_user(
|
|
34
|
+
credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)],
|
|
35
|
+
) -> dict:
|
|
36
|
+
token = credentials.credentials
|
|
37
|
+
try:
|
|
38
|
+
jwks = await _get_jwks()
|
|
39
|
+
unverified_header = jwt.get_unverified_header(token)
|
|
40
|
+
key = next(
|
|
41
|
+
(k for k in jwks["keys"] if k["kid"] == unverified_header.get("kid")),
|
|
42
|
+
None,
|
|
43
|
+
)
|
|
44
|
+
if key is None:
|
|
45
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unknown signing key")
|
|
46
|
+
|
|
47
|
+
payload = jwt.decode(
|
|
48
|
+
token,
|
|
49
|
+
key,
|
|
50
|
+
algorithms=["RS256"],
|
|
51
|
+
audience=settings.keycloak_client_id,
|
|
52
|
+
options={"verify_exp": True},
|
|
53
|
+
)
|
|
54
|
+
return payload
|
|
55
|
+
except JWTError as exc:
|
|
56
|
+
raise HTTPException(
|
|
57
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
58
|
+
detail=f"Invalid token: {exc}",
|
|
59
|
+
) from exc
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def user_id(user: Annotated[dict, Depends(get_current_user)]) -> str:
|
|
63
|
+
return user["sub"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def user_id_or_bot(request: Request, db: AsyncSession = Depends(get_db)) -> str:
|
|
67
|
+
"""Accept either JWT auth or X-User-Id header (when X-Bot-Token matches)."""
|
|
68
|
+
s = get_settings()
|
|
69
|
+
bot_token = request.headers.get("X-Bot-Token", "")
|
|
70
|
+
if bot_token and s.telegram_bot_token and bot_token == s.telegram_bot_token:
|
|
71
|
+
uid = request.headers.get("X-User-Id")
|
|
72
|
+
if uid:
|
|
73
|
+
return uid
|
|
74
|
+
# Fall back to normal JWT auth — extract bearer token from request directly
|
|
75
|
+
auth_header = request.headers.get("Authorization", "")
|
|
76
|
+
if not auth_header.startswith("Bearer "):
|
|
77
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
|
|
78
|
+
from fastapi.security import HTTPAuthorizationCredentials
|
|
79
|
+
credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials=auth_header[7:])
|
|
80
|
+
user = await get_current_user(credentials)
|
|
81
|
+
return user["sub"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pydantic_settings import BaseSettings
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Settings(BaseSettings):
|
|
6
|
+
# App
|
|
7
|
+
app_name: str = "Travel Chaos Organizer API"
|
|
8
|
+
debug: bool = False
|
|
9
|
+
upload_dir: str = "/data/uploads"
|
|
10
|
+
app_url: str = "https://tco.app"
|
|
11
|
+
|
|
12
|
+
# Database
|
|
13
|
+
database_url: str = "postgresql+asyncpg://tco:tco@localhost:5432/tco"
|
|
14
|
+
|
|
15
|
+
# Keycloak
|
|
16
|
+
keycloak_url: str = "http://localhost:8080"
|
|
17
|
+
keycloak_realm: str = "travel-chaos"
|
|
18
|
+
keycloak_client_id: str = "tco-backend"
|
|
19
|
+
|
|
20
|
+
# Ollama
|
|
21
|
+
ollama_url: str = "http://localhost:11434"
|
|
22
|
+
ollama_model: str = "llama3.2-vision"
|
|
23
|
+
ollama_timeout: int = 120
|
|
24
|
+
|
|
25
|
+
# Cachly Redis (optional) — caches Ollama parse results by content hash.
|
|
26
|
+
# Set to your Cachly Redis connection string to enable deduplication.
|
|
27
|
+
# Format: redis://:<password>@<host>:<port>/0 or rediss:// for TLS
|
|
28
|
+
cachly_redis_url: str = ""
|
|
29
|
+
|
|
30
|
+
# CORS
|
|
31
|
+
cors_origins: str = "*" # Set to your domain in production: https://yourapp.com
|
|
32
|
+
|
|
33
|
+
# Telegram notifier (optional)
|
|
34
|
+
telegram_bot_token: str = ""
|
|
35
|
+
telegram_chat_id: str = "" # default chat; per-app override below
|
|
36
|
+
tco_telegram_chat_id: str = "" # if set, overrides telegram_chat_id for TCO events
|
|
37
|
+
|
|
38
|
+
# Email (Resend)
|
|
39
|
+
resend_api_key: str = ""
|
|
40
|
+
resend_from: str = "noreply@tco.app"
|
|
41
|
+
|
|
42
|
+
# Admin
|
|
43
|
+
admin_user: str = "admin"
|
|
44
|
+
admin_password: str = "changeme"
|
|
45
|
+
|
|
46
|
+
# Sentry (optional)
|
|
47
|
+
sentry_dsn: str = ""
|
|
48
|
+
environment: str = "production"
|
|
49
|
+
|
|
50
|
+
# Upload
|
|
51
|
+
max_upload_bytes: int = 20 * 1024 * 1024 # 20 MB
|
|
52
|
+
|
|
53
|
+
# Stripe
|
|
54
|
+
stripe_secret_key: str = ""
|
|
55
|
+
stripe_webhook_secret: str = ""
|
|
56
|
+
stripe_pro_price_id: str = "price_YOUR_PRICE_ID"
|
|
57
|
+
stripe_success_url: str = "tco://upgrade/success"
|
|
58
|
+
stripe_cancel_url: str = "tco://upgrade/cancel"
|
|
59
|
+
|
|
60
|
+
class Config:
|
|
61
|
+
env_file = ".env"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@lru_cache
|
|
65
|
+
def get_settings() -> Settings:
|
|
66
|
+
return Settings()
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
|
2
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
3
|
+
from app.config import get_settings
|
|
4
|
+
|
|
5
|
+
settings = get_settings()
|
|
6
|
+
|
|
7
|
+
engine = create_async_engine(settings.database_url, echo=settings.debug)
|
|
8
|
+
AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Base(DeclarativeBase):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def get_db() -> AsyncSession:
|
|
16
|
+
async with AsyncSessionLocal() as session:
|
|
17
|
+
try:
|
|
18
|
+
yield session
|
|
19
|
+
finally:
|
|
20
|
+
await session.close()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
-- Travel Chaos Organizer — Initial Schema
|
|
2
|
+
|
|
3
|
+
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS trips (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
user_id TEXT NOT NULL,
|
|
8
|
+
name TEXT NOT NULL,
|
|
9
|
+
description TEXT,
|
|
10
|
+
start_date DATE,
|
|
11
|
+
end_date DATE,
|
|
12
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
13
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX idx_trips_user_id ON trips(user_id);
|
|
17
|
+
|
|
18
|
+
CREATE TYPE item_type AS ENUM (
|
|
19
|
+
'flight', 'train', 'bus', 'hotel', 'rental_car',
|
|
20
|
+
'activity', 'transfer', 'document', 'other'
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS trip_items (
|
|
24
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
25
|
+
trip_id UUID NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
|
|
26
|
+
user_id TEXT NOT NULL,
|
|
27
|
+
type item_type NOT NULL DEFAULT 'other',
|
|
28
|
+
title TEXT NOT NULL,
|
|
29
|
+
raw_text TEXT,
|
|
30
|
+
parsed_data JSONB,
|
|
31
|
+
event_at TIMESTAMPTZ,
|
|
32
|
+
event_end_at TIMESTAMPTZ,
|
|
33
|
+
booking_ref TEXT,
|
|
34
|
+
provider TEXT,
|
|
35
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
36
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE INDEX idx_trip_items_trip_id ON trip_items(trip_id);
|
|
40
|
+
CREATE INDEX idx_trip_items_event_at ON trip_items(event_at);
|
|
41
|
+
CREATE INDEX idx_trip_items_parsed_data ON trip_items USING gin(parsed_data);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS attachments (
|
|
44
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
45
|
+
trip_item_id UUID REFERENCES trip_items(id) ON DELETE CASCADE,
|
|
46
|
+
inbox_id UUID,
|
|
47
|
+
user_id TEXT NOT NULL,
|
|
48
|
+
file_path TEXT NOT NULL,
|
|
49
|
+
file_name TEXT NOT NULL,
|
|
50
|
+
mime_type TEXT NOT NULL,
|
|
51
|
+
size_bytes BIGINT,
|
|
52
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE INDEX idx_attachments_trip_item_id ON attachments(trip_item_id);
|
|
56
|
+
|
|
57
|
+
CREATE TYPE inbox_status AS ENUM ('pending', 'processed', 'rejected', 'assigned');
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS chaos_inbox (
|
|
60
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
61
|
+
user_id TEXT NOT NULL,
|
|
62
|
+
raw_content TEXT,
|
|
63
|
+
source TEXT,
|
|
64
|
+
status inbox_status NOT NULL DEFAULT 'pending',
|
|
65
|
+
parsed_data JSONB,
|
|
66
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
67
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE INDEX idx_chaos_inbox_user_id ON chaos_inbox(user_id);
|
|
71
|
+
CREATE INDEX idx_chaos_inbox_status ON chaos_inbox(status);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- users table: freemium plan tracking. id = Keycloak sub.
|
|
2
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
email TEXT,
|
|
5
|
+
plan TEXT NOT NULL DEFAULT 'free',
|
|
6
|
+
plan_expires_at TIMESTAMPTZ,
|
|
7
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
8
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
-- events table: telemetry. user_id nullable for anonymous/server events.
|
|
12
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
13
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
14
|
+
user_id TEXT,
|
|
15
|
+
event_name TEXT NOT NULL,
|
|
16
|
+
properties JSONB,
|
|
17
|
+
platform TEXT,
|
|
18
|
+
app_version TEXT,
|
|
19
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
20
|
+
);
|
|
21
|
+
CREATE INDEX idx_events_user_id ON events(user_id);
|
|
22
|
+
CREATE INDEX idx_events_name ON events(event_name);
|
|
23
|
+
CREATE INDEX idx_events_created ON events(created_at DESC);
|
|
24
|
+
|
|
25
|
+
-- waitlist: email capture from landing page (no auth required)
|
|
26
|
+
CREATE TABLE IF NOT EXISTS waitlist (
|
|
27
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
28
|
+
email TEXT NOT NULL UNIQUE,
|
|
29
|
+
source TEXT DEFAULT 'landing',
|
|
30
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
31
|
+
);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- Add telegram_chat_id column to users table.
|
|
2
|
+
-- This column was referenced in the application but missing from the initial schema.
|
|
3
|
+
ALTER TABLE users ADD COLUMN IF NOT EXISTS telegram_chat_id TEXT;
|
|
4
|
+
CREATE INDEX IF NOT EXISTS idx_users_telegram_chat_id ON users(telegram_chat_id) WHERE telegram_chat_id IS NOT NULL;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Add stripe_customer_id to users table.
|
|
2
|
+
-- Used by payments.py to link Stripe customer to user in webhook handlers.
|
|
3
|
+
-- Missing column causes crash on every Stripe webhook (checkout, subscription cancel, renewal, payment failed).
|
|
4
|
+
ALTER TABLE users ADD COLUMN IF NOT EXISTS stripe_customer_id TEXT;
|
|
5
|
+
CREATE INDEX IF NOT EXISTS idx_users_stripe_customer_id ON users(stripe_customer_id) WHERE stripe_customer_id IS NOT NULL;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JsonFormatter(logging.Formatter):
|
|
8
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
9
|
+
return json.dumps({
|
|
10
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
11
|
+
"level": record.levelname,
|
|
12
|
+
"logger": record.name,
|
|
13
|
+
"msg": record.getMessage(),
|
|
14
|
+
**({"exc": self.formatException(record.exc_info)} if record.exc_info else {}),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def setup_logging(debug: bool = False) -> None:
|
|
19
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
20
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
21
|
+
handler.setFormatter(JsonFormatter())
|
|
22
|
+
logging.basicConfig(level=level, handlers=[handler], force=True)
|
|
23
|
+
# Silence noisy libraries
|
|
24
|
+
for name in ("uvicorn.access", "sqlalchemy.engine", "asyncio"):
|
|
25
|
+
logging.getLogger(name).setLevel(logging.WARNING)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
6
|
+
from app.config import get_settings
|
|
7
|
+
from app.routers import trips, items, parse, inbox, mail, events as events_router, waitlist as waitlist_router
|
|
8
|
+
from app.routers import admin as admin_router
|
|
9
|
+
from app.routers import users as users_router
|
|
10
|
+
from app.routers import payments as payments_router
|
|
11
|
+
from app.routers import export as export_router
|
|
12
|
+
from app.routers import cache as cache_router
|
|
13
|
+
from app.services.cache import cachly_configured
|
|
14
|
+
from app.limiter import limiter
|
|
15
|
+
from slowapi import _rate_limit_exceeded_handler
|
|
16
|
+
from slowapi.errors import RateLimitExceeded
|
|
17
|
+
from slowapi.middleware import SlowAPIMiddleware
|
|
18
|
+
|
|
19
|
+
settings = get_settings()
|
|
20
|
+
|
|
21
|
+
APP_VERSION = "0.1.0"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@asynccontextmanager
|
|
25
|
+
async def lifespan(app: FastAPI):
|
|
26
|
+
from app.logging_config import setup_logging
|
|
27
|
+
setup_logging(debug=settings.debug)
|
|
28
|
+
from app.services import sentry as sentry_svc
|
|
29
|
+
sentry_svc.init(settings.sentry_dsn, settings.environment, release=APP_VERSION)
|
|
30
|
+
Path(settings.upload_dir).mkdir(parents=True, exist_ok=True)
|
|
31
|
+
import sys
|
|
32
|
+
if settings.admin_password == "changeme":
|
|
33
|
+
print("⚠️ WARNING: ADMIN_PASSWORD is still 'changeme'. Change it before going live!", file=sys.stderr)
|
|
34
|
+
yield
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
app = FastAPI(
|
|
38
|
+
title=settings.app_name,
|
|
39
|
+
version=APP_VERSION,
|
|
40
|
+
lifespan=lifespan,
|
|
41
|
+
docs_url="/docs" if settings.debug else None,
|
|
42
|
+
redoc_url="/redoc" if settings.debug else None,
|
|
43
|
+
openapi_url="/openapi.json" if settings.debug else None,
|
|
44
|
+
)
|
|
45
|
+
app.state.limiter = limiter
|
|
46
|
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
47
|
+
app.add_middleware(SlowAPIMiddleware)
|
|
48
|
+
|
|
49
|
+
app.add_middleware(
|
|
50
|
+
CORSMiddleware,
|
|
51
|
+
allow_origins=settings.cors_origins.split(",") if settings.cors_origins != "*" else ["*"],
|
|
52
|
+
allow_credentials=True,
|
|
53
|
+
allow_methods=["*"],
|
|
54
|
+
allow_headers=["*"],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
app.include_router(trips.router, prefix="/api/v1")
|
|
58
|
+
app.include_router(items.router, prefix="/api/v1")
|
|
59
|
+
app.include_router(parse.router, prefix="/api/v1")
|
|
60
|
+
app.include_router(inbox.router, prefix="/api/v1")
|
|
61
|
+
app.include_router(mail.router, prefix="/api/v1")
|
|
62
|
+
app.include_router(events_router.router, prefix="/api/v1")
|
|
63
|
+
app.include_router(waitlist_router.router, prefix="/api/v1")
|
|
64
|
+
app.include_router(admin_router.router) # no /api/v1 prefix — admin is at /admin
|
|
65
|
+
app.include_router(users_router.router, prefix="/api/v1")
|
|
66
|
+
app.include_router(payments_router.router, prefix="/api/v1")
|
|
67
|
+
app.include_router(export_router.router, prefix="/api/v1")
|
|
68
|
+
app.include_router(cache_router.router, prefix="/api/v1")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.get("/health")
|
|
72
|
+
async def health():
|
|
73
|
+
return {
|
|
74
|
+
"status": "ok",
|
|
75
|
+
"version": APP_VERSION,
|
|
76
|
+
"cachly_cache": "enabled" if cachly_configured() else "disabled",
|
|
77
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ItemType = Literal[
|
|
9
|
+
"flight", "train", "bus", "hotel", "rental_car",
|
|
10
|
+
"activity", "transfer", "document", "other"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
InboxStatus = Literal["pending", "processed", "rejected", "assigned"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── Trips ──────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
class TripCreate(BaseModel):
|
|
19
|
+
name: str = Field(..., min_length=1, max_length=200)
|
|
20
|
+
description: str | None = None
|
|
21
|
+
start_date: date | None = None
|
|
22
|
+
end_date: date | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TripUpdate(BaseModel):
|
|
26
|
+
name: str | None = Field(None, min_length=1, max_length=200)
|
|
27
|
+
description: str | None = None
|
|
28
|
+
start_date: date | None = None
|
|
29
|
+
end_date: date | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TripOut(BaseModel):
|
|
33
|
+
id: UUID
|
|
34
|
+
user_id: str
|
|
35
|
+
name: str
|
|
36
|
+
description: str | None
|
|
37
|
+
start_date: date | None
|
|
38
|
+
end_date: date | None
|
|
39
|
+
created_at: datetime
|
|
40
|
+
updated_at: datetime
|
|
41
|
+
|
|
42
|
+
model_config = {"from_attributes": True}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ── Trip Items ─────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
class TripItemCreate(BaseModel):
|
|
48
|
+
type: ItemType = "other"
|
|
49
|
+
title: str = Field(..., min_length=1, max_length=300)
|
|
50
|
+
raw_text: str | None = None
|
|
51
|
+
parsed_data: dict[str, Any] | None = None
|
|
52
|
+
event_at: datetime | None = None
|
|
53
|
+
event_end_at: datetime | None = None
|
|
54
|
+
booking_ref: str | None = None
|
|
55
|
+
provider: str | None = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TripItemUpdate(BaseModel):
|
|
59
|
+
type: ItemType | None = None
|
|
60
|
+
title: str | None = Field(None, min_length=1, max_length=300)
|
|
61
|
+
raw_text: str | None = None
|
|
62
|
+
parsed_data: dict[str, Any] | None = None
|
|
63
|
+
event_at: datetime | None = None
|
|
64
|
+
event_end_at: datetime | None = None
|
|
65
|
+
booking_ref: str | None = None
|
|
66
|
+
provider: str | None = None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TripItemOut(BaseModel):
|
|
70
|
+
id: UUID
|
|
71
|
+
trip_id: UUID
|
|
72
|
+
user_id: str
|
|
73
|
+
type: str
|
|
74
|
+
title: str
|
|
75
|
+
raw_text: str | None
|
|
76
|
+
parsed_data: dict[str, Any] | None
|
|
77
|
+
event_at: datetime | None
|
|
78
|
+
event_end_at: datetime | None
|
|
79
|
+
booking_ref: str | None
|
|
80
|
+
provider: str | None
|
|
81
|
+
created_at: datetime
|
|
82
|
+
updated_at: datetime
|
|
83
|
+
|
|
84
|
+
model_config = {"from_attributes": True}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ── Attachments ────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
class AttachmentOut(BaseModel):
|
|
90
|
+
id: UUID
|
|
91
|
+
trip_item_id: UUID | None
|
|
92
|
+
inbox_id: UUID | None
|
|
93
|
+
file_name: str
|
|
94
|
+
mime_type: str
|
|
95
|
+
size_bytes: int | None
|
|
96
|
+
created_at: datetime
|
|
97
|
+
|
|
98
|
+
model_config = {"from_attributes": True}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ── Chaos Inbox ────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
class InboxItemOut(BaseModel):
|
|
104
|
+
id: UUID
|
|
105
|
+
user_id: str
|
|
106
|
+
raw_content: str | None
|
|
107
|
+
source: str | None
|
|
108
|
+
status: str
|
|
109
|
+
parsed_data: dict[str, Any] | None
|
|
110
|
+
created_at: datetime
|
|
111
|
+
updated_at: datetime
|
|
112
|
+
|
|
113
|
+
model_config = {"from_attributes": True}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class InboxAssign(BaseModel):
|
|
117
|
+
trip_id: UUID
|
|
118
|
+
type: ItemType = "other"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ── Parsing ────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
class ParsedTravelData(BaseModel):
|
|
124
|
+
type: ItemType = "other"
|
|
125
|
+
title: str = "Unnamed Item"
|
|
126
|
+
booking_ref: str | None = None
|
|
127
|
+
provider: str | None = None
|
|
128
|
+
event_at: str | None = None
|
|
129
|
+
event_end_at: str | None = None
|
|
130
|
+
origin: str | None = None
|
|
131
|
+
destination: str | None = None
|
|
132
|
+
passengers: list[str] | None = None
|
|
133
|
+
confirmation_number: str | None = None
|
|
134
|
+
price: str | None = None
|
|
135
|
+
raw_summary: str | None = None
|
|
136
|
+
confidence: float = 0.0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ParseResponse(BaseModel):
|
|
140
|
+
parsed: ParsedTravelData
|
|
141
|
+
raw_text: str | None = None
|
|
142
|
+
model_used: str
|
|
File without changes
|