@weirdfingers/baseboards 0.9.6 → 0.9.7
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/dist/index.js +560 -469
- package/dist/index.js.map +1 -1
- package/package.json +2 -5
- package/templates/README.md +0 -122
- package/templates/api/.env.example +0 -65
- package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +0 -148
- package/templates/api/Dockerfile +0 -32
- package/templates/api/README.md +0 -264
- package/templates/api/alembic/env.py +0 -114
- package/templates/api/alembic/script.py.mako +0 -28
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +0 -506
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +0 -75
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +0 -467
- package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +0 -134
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +0 -88
- package/templates/api/alembic.ini +0 -36
- package/templates/api/config/generators.yaml +0 -237
- package/templates/api/config/storage_config.yaml +0 -26
- package/templates/api/docs/ADDING_GENERATORS.md +0 -409
- package/templates/api/docs/GENERATORS_API.md +0 -502
- package/templates/api/docs/MIGRATIONS.md +0 -472
- package/templates/api/docs/TESTING_LIVE_APIS.md +0 -417
- package/templates/api/docs/storage_providers.md +0 -337
- package/templates/api/pyproject.toml +0 -205
- package/templates/api/src/boards/__init__.py +0 -10
- package/templates/api/src/boards/api/app.py +0 -172
- package/templates/api/src/boards/api/auth.py +0 -75
- package/templates/api/src/boards/api/endpoints/__init__.py +0 -3
- package/templates/api/src/boards/api/endpoints/jobs.py +0 -76
- package/templates/api/src/boards/api/endpoints/setup.py +0 -505
- package/templates/api/src/boards/api/endpoints/sse.py +0 -129
- package/templates/api/src/boards/api/endpoints/storage.py +0 -155
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +0 -296
- package/templates/api/src/boards/api/endpoints/uploads.py +0 -149
- package/templates/api/src/boards/api/endpoints/webhooks.py +0 -13
- package/templates/api/src/boards/auth/__init__.py +0 -15
- package/templates/api/src/boards/auth/adapters/__init__.py +0 -27
- package/templates/api/src/boards/auth/adapters/auth0.py +0 -220
- package/templates/api/src/boards/auth/adapters/base.py +0 -73
- package/templates/api/src/boards/auth/adapters/clerk.py +0 -172
- package/templates/api/src/boards/auth/adapters/jwt.py +0 -122
- package/templates/api/src/boards/auth/adapters/none.py +0 -102
- package/templates/api/src/boards/auth/adapters/oidc.py +0 -284
- package/templates/api/src/boards/auth/adapters/supabase.py +0 -110
- package/templates/api/src/boards/auth/context.py +0 -35
- package/templates/api/src/boards/auth/factory.py +0 -129
- package/templates/api/src/boards/auth/middleware.py +0 -221
- package/templates/api/src/boards/auth/provisioning.py +0 -129
- package/templates/api/src/boards/auth/tenant_extraction.py +0 -278
- package/templates/api/src/boards/cli.py +0 -354
- package/templates/api/src/boards/config.py +0 -131
- package/templates/api/src/boards/database/__init__.py +0 -7
- package/templates/api/src/boards/database/cli.py +0 -110
- package/templates/api/src/boards/database/connection.py +0 -292
- package/templates/api/src/boards/database/models.py +0 -19
- package/templates/api/src/boards/database/seed_data.py +0 -182
- package/templates/api/src/boards/dbmodels/__init__.py +0 -441
- package/templates/api/src/boards/generators/__init__.py +0 -57
- package/templates/api/src/boards/generators/artifact_resolution.py +0 -405
- package/templates/api/src/boards/generators/artifacts.py +0 -53
- package/templates/api/src/boards/generators/base.py +0 -144
- package/templates/api/src/boards/generators/implementations/__init__.py +0 -14
- package/templates/api/src/boards/generators/implementations/fal/__init__.py +0 -25
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +0 -23
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +0 -171
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_text_to_speech.py +0 -176
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_tts_turbo.py +0 -195
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +0 -206
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +0 -237
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +0 -63
- package/templates/api/src/boards/generators/implementations/fal/image/bytedance_seedream_v45_edit.py +0 -219
- package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +0 -220
- package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +0 -227
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +0 -203
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +0 -230
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +0 -204
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image_edit.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_15_edit.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_5.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +0 -182
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +0 -299
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +0 -226
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +0 -249
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +0 -244
- package/templates/api/src/boards/generators/implementations/fal/image/reve_edit.py +0 -178
- package/templates/api/src/boards/generators/implementations/fal/image/reve_text_to_image.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/utils.py +0 -61
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +0 -77
- package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +0 -161
- package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +0 -222
- package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +0 -152
- package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_pro.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_standard.py +0 -159
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +0 -153
- package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +0 -163
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/video/veed_fabric_1_0.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +0 -174
- package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/video/veo31.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast_image_to_video.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +0 -187
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_image_to_video.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_text_to_video.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +0 -158
- package/templates/api/src/boards/generators/implementations/kie/__init__.py +0 -11
- package/templates/api/src/boards/generators/implementations/kie/base.py +0 -316
- package/templates/api/src/boards/generators/implementations/kie/image/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/kie/image/nano_banana_edit.py +0 -190
- package/templates/api/src/boards/generators/implementations/kie/utils.py +0 -98
- package/templates/api/src/boards/generators/implementations/kie/video/__init__.py +0 -8
- package/templates/api/src/boards/generators/implementations/kie/video/veo3.py +0 -161
- package/templates/api/src/boards/generators/implementations/openai/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/whisper.py +0 -69
- package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/image/dalle3.py +0 -96
- package/templates/api/src/boards/generators/implementations/replicate/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/flux_pro.py +0 -88
- package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/video/lipsync.py +0 -73
- package/templates/api/src/boards/generators/loader.py +0 -253
- package/templates/api/src/boards/generators/registry.py +0 -114
- package/templates/api/src/boards/generators/resolution.py +0 -632
- package/templates/api/src/boards/generators/testmods/class_gen.py +0 -34
- package/templates/api/src/boards/generators/testmods/import_side_effect.py +0 -35
- package/templates/api/src/boards/graphql/__init__.py +0 -7
- package/templates/api/src/boards/graphql/access_control.py +0 -136
- package/templates/api/src/boards/graphql/mutations/root.py +0 -148
- package/templates/api/src/boards/graphql/queries/root.py +0 -116
- package/templates/api/src/boards/graphql/resolvers/__init__.py +0 -8
- package/templates/api/src/boards/graphql/resolvers/auth.py +0 -12
- package/templates/api/src/boards/graphql/resolvers/board.py +0 -1053
- package/templates/api/src/boards/graphql/resolvers/generation.py +0 -666
- package/templates/api/src/boards/graphql/resolvers/generator.py +0 -50
- package/templates/api/src/boards/graphql/resolvers/lineage.py +0 -381
- package/templates/api/src/boards/graphql/resolvers/upload.py +0 -463
- package/templates/api/src/boards/graphql/resolvers/user.py +0 -25
- package/templates/api/src/boards/graphql/schema.py +0 -81
- package/templates/api/src/boards/graphql/types/board.py +0 -102
- package/templates/api/src/boards/graphql/types/generation.py +0 -166
- package/templates/api/src/boards/graphql/types/generator.py +0 -17
- package/templates/api/src/boards/graphql/types/user.py +0 -47
- package/templates/api/src/boards/jobs/repository.py +0 -153
- package/templates/api/src/boards/logging.py +0 -195
- package/templates/api/src/boards/middleware.py +0 -339
- package/templates/api/src/boards/progress/__init__.py +0 -4
- package/templates/api/src/boards/progress/models.py +0 -25
- package/templates/api/src/boards/progress/publisher.py +0 -64
- package/templates/api/src/boards/py.typed +0 -0
- package/templates/api/src/boards/redis_pool.py +0 -118
- package/templates/api/src/boards/storage/__init__.py +0 -52
- package/templates/api/src/boards/storage/base.py +0 -363
- package/templates/api/src/boards/storage/config.py +0 -187
- package/templates/api/src/boards/storage/factory.py +0 -288
- package/templates/api/src/boards/storage/implementations/__init__.py +0 -27
- package/templates/api/src/boards/storage/implementations/gcs.py +0 -340
- package/templates/api/src/boards/storage/implementations/local.py +0 -201
- package/templates/api/src/boards/storage/implementations/s3.py +0 -294
- package/templates/api/src/boards/storage/implementations/supabase.py +0 -218
- package/templates/api/src/boards/tenant_isolation.py +0 -446
- package/templates/api/src/boards/validation.py +0 -262
- package/templates/api/src/boards/workers/__init__.py +0 -1
- package/templates/api/src/boards/workers/actors.py +0 -274
- package/templates/api/src/boards/workers/cli.py +0 -125
- package/templates/api/src/boards/workers/context.py +0 -348
- package/templates/api/src/boards/workers/middleware.py +0 -58
- package/templates/api/src/py.typed +0 -0
- package/templates/compose.web.yaml +0 -35
- package/templates/compose.yaml +0 -116
- package/templates/docker/env.example +0 -23
- package/templates/web/.env.example +0 -28
- package/templates/web/Dockerfile +0 -51
- package/templates/web/components.json +0 -22
- package/templates/web/imageLoader.js +0 -18
- package/templates/web/next-env.d.ts +0 -5
- package/templates/web/next.config.js +0 -36
- package/templates/web/package.json +0 -41
- package/templates/web/postcss.config.mjs +0 -7
- package/templates/web/public/favicon.ico +0 -0
- package/templates/web/src/app/boards/[boardId]/page.tsx +0 -353
- package/templates/web/src/app/globals.css +0 -123
- package/templates/web/src/app/layout.tsx +0 -31
- package/templates/web/src/app/lineage/[generationId]/page.tsx +0 -235
- package/templates/web/src/app/page.tsx +0 -35
- package/templates/web/src/app/providers.tsx +0 -18
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +0 -206
- package/templates/web/src/components/boards/ArtifactPreview.tsx +0 -466
- package/templates/web/src/components/boards/GenerationGrid.tsx +0 -282
- package/templates/web/src/components/boards/GenerationInput.tsx +0 -370
- package/templates/web/src/components/boards/GeneratorSelector.tsx +0 -272
- package/templates/web/src/components/boards/UploadArtifact.tsx +0 -563
- package/templates/web/src/components/header.tsx +0 -32
- package/templates/web/src/components/theme-provider.tsx +0 -10
- package/templates/web/src/components/theme-toggle.tsx +0 -75
- package/templates/web/src/components/ui/alert-dialog.tsx +0 -157
- package/templates/web/src/components/ui/button.tsx +0 -58
- package/templates/web/src/components/ui/card.tsx +0 -92
- package/templates/web/src/components/ui/dropdown-menu.tsx +0 -200
- package/templates/web/src/components/ui/navigation-menu.tsx +0 -168
- package/templates/web/src/components/ui/toast.tsx +0 -128
- package/templates/web/src/components/ui/toaster.tsx +0 -35
- package/templates/web/src/components/ui/use-toast.ts +0 -187
- package/templates/web/src/hooks/useGeneratorMRU.ts +0 -57
- package/templates/web/src/lib/utils.ts +0 -6
- package/templates/web/tsconfig.json +0 -41
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Middleware for request context and logging
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import re
|
|
7
|
-
from collections.abc import Callable
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from fastapi import Request, Response
|
|
11
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
12
|
-
|
|
13
|
-
from .config import settings
|
|
14
|
-
from .logging import (
|
|
15
|
-
clear_request_context,
|
|
16
|
-
extract_user_id_from_request,
|
|
17
|
-
get_logger,
|
|
18
|
-
set_request_context,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
logger = get_logger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def sanitize_query_params(params: dict[str, Any]) -> dict[str, Any]:
|
|
25
|
-
"""Remove sensitive query parameters from logging.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
params: Dictionary of query parameters
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
Dictionary with sensitive parameters redacted
|
|
32
|
-
"""
|
|
33
|
-
sensitive_keys = {
|
|
34
|
-
"password",
|
|
35
|
-
"token",
|
|
36
|
-
"api_key",
|
|
37
|
-
"secret",
|
|
38
|
-
"auth",
|
|
39
|
-
"authorization",
|
|
40
|
-
"access_token",
|
|
41
|
-
"refresh_token",
|
|
42
|
-
"key",
|
|
43
|
-
"private_key",
|
|
44
|
-
"jwt",
|
|
45
|
-
"session",
|
|
46
|
-
"session_id",
|
|
47
|
-
"cookie",
|
|
48
|
-
"credentials",
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
sanitized = {}
|
|
52
|
-
for key, value in params.items():
|
|
53
|
-
# Check if any sensitive keyword is in the parameter name (case insensitive)
|
|
54
|
-
if any(sensitive in key.lower() for sensitive in sensitive_keys):
|
|
55
|
-
sanitized[key] = "[REDACTED]"
|
|
56
|
-
else:
|
|
57
|
-
sanitized[key] = value
|
|
58
|
-
|
|
59
|
-
return sanitized
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# 1) Extend extractor to support GET /graphql as well
|
|
63
|
-
async def extract_graphql_operation_name(request: Request) -> str | None:
|
|
64
|
-
if request.url.path != "/graphql":
|
|
65
|
-
return None
|
|
66
|
-
|
|
67
|
-
# Handle GET /graphql (operationName in query params, or parse from query)
|
|
68
|
-
if request.method == "GET":
|
|
69
|
-
params = dict(request.query_params)
|
|
70
|
-
op = params.get("operationName")
|
|
71
|
-
if isinstance(op, str) and op:
|
|
72
|
-
return op
|
|
73
|
-
|
|
74
|
-
q = params.get("query", "")
|
|
75
|
-
if not isinstance(q, str) or not q:
|
|
76
|
-
return None
|
|
77
|
-
if "__schema" in q or "IntrospectionQuery" in q:
|
|
78
|
-
return "__introspection"
|
|
79
|
-
|
|
80
|
-
match = re.search(r"\bquery\s+(\w+)", q) or re.search(r"\bmutation\s+(\w+)", q)
|
|
81
|
-
if match:
|
|
82
|
-
kind = "mutation:" if q.lstrip().startswith("mutation") else ""
|
|
83
|
-
return f"{kind}{match.group(1)}"
|
|
84
|
-
return "unnamed_operation"
|
|
85
|
-
|
|
86
|
-
# Existing POST /graphql logic
|
|
87
|
-
if request.method == "POST":
|
|
88
|
-
try:
|
|
89
|
-
body = await request.body()
|
|
90
|
-
if not body:
|
|
91
|
-
return None
|
|
92
|
-
data = json.loads(body)
|
|
93
|
-
op = data.get("operationName")
|
|
94
|
-
if isinstance(op, str) and op:
|
|
95
|
-
return op
|
|
96
|
-
q = data.get("query", "")
|
|
97
|
-
if not isinstance(q, str) or not q:
|
|
98
|
-
return None
|
|
99
|
-
if "__schema" in q or "IntrospectionQuery" in q:
|
|
100
|
-
return "__introspection"
|
|
101
|
-
m = re.search(r"\bquery\s+(\w+)", q) or re.search(r"\bmutation\s+(\w+)", q)
|
|
102
|
-
if m:
|
|
103
|
-
kind = "mutation:" if q.lstrip().startswith("mutation") else ""
|
|
104
|
-
return f"{kind}{m.group(1)}"
|
|
105
|
-
return "unnamed_operation"
|
|
106
|
-
except (json.JSONDecodeError, TypeError):
|
|
107
|
-
return None
|
|
108
|
-
|
|
109
|
-
return None
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class LoggingContextMiddleware(BaseHTTPMiddleware):
|
|
113
|
-
"""Middleware to set logging context for each request."""
|
|
114
|
-
|
|
115
|
-
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
116
|
-
"""Process request and set logging context."""
|
|
117
|
-
|
|
118
|
-
# Extract user ID from request (implement based on your auth)
|
|
119
|
-
user_id = extract_user_id_from_request(request)
|
|
120
|
-
|
|
121
|
-
# Set request context
|
|
122
|
-
set_request_context(user_id=user_id)
|
|
123
|
-
|
|
124
|
-
try:
|
|
125
|
-
# Sanitize query parameters for logging
|
|
126
|
-
sanitized_params = None
|
|
127
|
-
if request.query_params:
|
|
128
|
-
sanitized_params = sanitize_query_params(dict(request.query_params))
|
|
129
|
-
# If hitting /graphql, never log raw GraphQL payload in query string
|
|
130
|
-
# Comment this out to see the raw GraphQL payload in the query string
|
|
131
|
-
if request.url.path == "/graphql" and isinstance(sanitized_params, dict):
|
|
132
|
-
for k in ("query", "variables", "extensions"):
|
|
133
|
-
if k in sanitized_params:
|
|
134
|
-
sanitized_params[k] = "[REDACTED]"
|
|
135
|
-
|
|
136
|
-
# Extract GraphQL operation name if applicable
|
|
137
|
-
graphql_operation = await extract_graphql_operation_name(request)
|
|
138
|
-
|
|
139
|
-
# Log request start
|
|
140
|
-
log_data = {
|
|
141
|
-
"method": request.method,
|
|
142
|
-
"path": request.url.path,
|
|
143
|
-
"query_params": sanitized_params,
|
|
144
|
-
"user_agent": request.headers.get("user-agent"),
|
|
145
|
-
"remote_addr": request.client.host if request.client else None,
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
# Add GraphQL operation name if available
|
|
149
|
-
if graphql_operation:
|
|
150
|
-
log_data["graphql_operation"] = graphql_operation
|
|
151
|
-
|
|
152
|
-
logger.info("Request started", **log_data)
|
|
153
|
-
|
|
154
|
-
# Process request
|
|
155
|
-
response = await call_next(request)
|
|
156
|
-
|
|
157
|
-
# Log request completion
|
|
158
|
-
logger.info(
|
|
159
|
-
"Request completed",
|
|
160
|
-
status_code=response.status_code,
|
|
161
|
-
method=request.method,
|
|
162
|
-
path=request.url.path,
|
|
163
|
-
graphql_operation=graphql_operation, # Include in completion log too
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
return response
|
|
167
|
-
|
|
168
|
-
except Exception as e:
|
|
169
|
-
# Log request error
|
|
170
|
-
logger.error(
|
|
171
|
-
"Request failed",
|
|
172
|
-
method=request.method,
|
|
173
|
-
path=request.url.path,
|
|
174
|
-
error=str(e),
|
|
175
|
-
)
|
|
176
|
-
raise
|
|
177
|
-
|
|
178
|
-
finally:
|
|
179
|
-
# Clear context
|
|
180
|
-
clear_request_context()
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class TenantRoutingMiddleware(BaseHTTPMiddleware):
|
|
184
|
-
"""
|
|
185
|
-
Middleware for tenant-aware request routing and validation.
|
|
186
|
-
|
|
187
|
-
This middleware:
|
|
188
|
-
1. Validates X-Tenant headers in multi-tenant mode
|
|
189
|
-
2. Enforces tenant isolation rules
|
|
190
|
-
3. Sets up request context for tenant-scoped operations
|
|
191
|
-
4. Provides early tenant validation before auth processing
|
|
192
|
-
"""
|
|
193
|
-
|
|
194
|
-
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
195
|
-
"""Process request with tenant routing and validation."""
|
|
196
|
-
|
|
197
|
-
# Extract tenant information from headers
|
|
198
|
-
x_tenant = request.headers.get("X-Tenant")
|
|
199
|
-
|
|
200
|
-
# Log incoming request with tenant info
|
|
201
|
-
logger.debug(
|
|
202
|
-
"Processing request with tenant context",
|
|
203
|
-
method=request.method,
|
|
204
|
-
path=request.url.path,
|
|
205
|
-
x_tenant=x_tenant,
|
|
206
|
-
multi_tenant_mode=settings.multi_tenant_mode,
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
# Validate tenant header in multi-tenant mode
|
|
210
|
-
if settings.multi_tenant_mode:
|
|
211
|
-
tenant_validation_result = await self._validate_tenant_header(x_tenant, request)
|
|
212
|
-
if tenant_validation_result is not None:
|
|
213
|
-
return tenant_validation_result
|
|
214
|
-
|
|
215
|
-
# Add tenant context to request state for downstream use
|
|
216
|
-
request.state.tenant_slug = x_tenant or settings.default_tenant_slug
|
|
217
|
-
request.state.multi_tenant_mode = settings.multi_tenant_mode
|
|
218
|
-
|
|
219
|
-
try:
|
|
220
|
-
response = await call_next(request)
|
|
221
|
-
|
|
222
|
-
# Add tenant information to response headers for debugging
|
|
223
|
-
if settings.debug:
|
|
224
|
-
response.headers["X-Tenant-Resolved"] = request.state.tenant_slug
|
|
225
|
-
if settings.multi_tenant_mode:
|
|
226
|
-
response.headers["X-Multi-Tenant-Mode"] = "true"
|
|
227
|
-
|
|
228
|
-
return response
|
|
229
|
-
|
|
230
|
-
except Exception as e:
|
|
231
|
-
logger.error(
|
|
232
|
-
"Request processing failed",
|
|
233
|
-
error=str(e),
|
|
234
|
-
tenant_slug=getattr(request.state, "tenant_slug", "unknown"),
|
|
235
|
-
path=request.url.path,
|
|
236
|
-
)
|
|
237
|
-
raise
|
|
238
|
-
|
|
239
|
-
async def _validate_tenant_header(
|
|
240
|
-
self, x_tenant: str | None, request: Request
|
|
241
|
-
) -> Response | None:
|
|
242
|
-
"""
|
|
243
|
-
Validate X-Tenant header in multi-tenant mode.
|
|
244
|
-
|
|
245
|
-
Returns:
|
|
246
|
-
Response if validation fails (error response), None if validation passes
|
|
247
|
-
"""
|
|
248
|
-
from fastapi.responses import JSONResponse
|
|
249
|
-
|
|
250
|
-
# In multi-tenant mode, some endpoints may require X-Tenant header
|
|
251
|
-
if self._requires_tenant_header(request):
|
|
252
|
-
if not x_tenant:
|
|
253
|
-
logger.warning(
|
|
254
|
-
"Missing required X-Tenant header in multi-tenant mode",
|
|
255
|
-
path=request.url.path,
|
|
256
|
-
method=request.method,
|
|
257
|
-
)
|
|
258
|
-
return JSONResponse(
|
|
259
|
-
status_code=400,
|
|
260
|
-
content={
|
|
261
|
-
"error": "Missing X-Tenant header",
|
|
262
|
-
"detail": (
|
|
263
|
-
"X-Tenant header is required in multi-tenant mode for this endpoint"
|
|
264
|
-
),
|
|
265
|
-
"multi_tenant_mode": True,
|
|
266
|
-
},
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
# Validate tenant slug format if provided
|
|
270
|
-
if x_tenant:
|
|
271
|
-
validation_error = self._validate_tenant_slug_format(x_tenant)
|
|
272
|
-
if validation_error:
|
|
273
|
-
logger.warning(
|
|
274
|
-
"Invalid X-Tenant header format",
|
|
275
|
-
x_tenant=x_tenant,
|
|
276
|
-
error=validation_error,
|
|
277
|
-
)
|
|
278
|
-
return JSONResponse(
|
|
279
|
-
status_code=400,
|
|
280
|
-
content={
|
|
281
|
-
"error": "Invalid X-Tenant header format",
|
|
282
|
-
"detail": validation_error,
|
|
283
|
-
"provided_tenant": x_tenant,
|
|
284
|
-
},
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
return None # Validation passed
|
|
288
|
-
|
|
289
|
-
def _requires_tenant_header(self, request: Request) -> bool:
|
|
290
|
-
"""
|
|
291
|
-
Determine if the request requires an X-Tenant header.
|
|
292
|
-
|
|
293
|
-
In multi-tenant mode, most API endpoints require tenant specification,
|
|
294
|
-
except for certain system endpoints like health checks.
|
|
295
|
-
"""
|
|
296
|
-
path = request.url.path
|
|
297
|
-
|
|
298
|
-
# System endpoints that don't require tenant specification
|
|
299
|
-
system_endpoints = {
|
|
300
|
-
"/health",
|
|
301
|
-
"/api/setup/status",
|
|
302
|
-
"/docs",
|
|
303
|
-
"/redoc",
|
|
304
|
-
"/openapi.json",
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if path in system_endpoints:
|
|
308
|
-
return False
|
|
309
|
-
|
|
310
|
-
# Setup endpoints are special - they help create tenants
|
|
311
|
-
if path.startswith("/api/setup/"):
|
|
312
|
-
return False
|
|
313
|
-
|
|
314
|
-
# All other API endpoints require tenant in multi-tenant mode
|
|
315
|
-
if path.startswith("/api/") or path.startswith("/graphql"):
|
|
316
|
-
return True
|
|
317
|
-
|
|
318
|
-
return False
|
|
319
|
-
|
|
320
|
-
def _validate_tenant_slug_format(self, tenant_slug: str) -> str | None:
|
|
321
|
-
"""
|
|
322
|
-
Validate tenant slug format.
|
|
323
|
-
|
|
324
|
-
Returns:
|
|
325
|
-
Error message if invalid, None if valid
|
|
326
|
-
"""
|
|
327
|
-
if not tenant_slug:
|
|
328
|
-
return "Tenant slug cannot be empty"
|
|
329
|
-
|
|
330
|
-
if len(tenant_slug) > 255:
|
|
331
|
-
return "Tenant slug too long (max 255 characters)"
|
|
332
|
-
|
|
333
|
-
if not re.match(r"^[a-z0-9-]+$", tenant_slug):
|
|
334
|
-
return "Tenant slug must contain only lowercase letters, numbers, and hyphens"
|
|
335
|
-
|
|
336
|
-
if tenant_slug.startswith("-") or tenant_slug.endswith("-"):
|
|
337
|
-
return "Tenant slug cannot start or end with hyphen"
|
|
338
|
-
|
|
339
|
-
return None # Valid
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"""Pydantic models for progress updates."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from datetime import UTC, datetime
|
|
6
|
-
from typing import Literal
|
|
7
|
-
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ArtifactInfo(BaseModel):
|
|
12
|
-
url: str
|
|
13
|
-
type: str
|
|
14
|
-
metadata: dict = {}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class ProgressUpdate(BaseModel):
|
|
18
|
-
job_id: str
|
|
19
|
-
status: str # Use string to avoid tight coupling to GraphQL enums
|
|
20
|
-
progress: float
|
|
21
|
-
phase: Literal["queued", "initializing", "processing", "finalizing"]
|
|
22
|
-
message: str | None = None
|
|
23
|
-
estimated_completion: datetime | None = None
|
|
24
|
-
artifacts: list[ArtifactInfo] = []
|
|
25
|
-
timestamp: datetime = datetime.now(UTC)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"""Publisher for progress updates via Redis pub/sub with DB persistence."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from ..config import Settings
|
|
6
|
-
from ..database.connection import get_async_session
|
|
7
|
-
from ..jobs import repository as jobs_repo
|
|
8
|
-
from ..logging import get_logger
|
|
9
|
-
from ..redis_pool import get_redis_client
|
|
10
|
-
from .models import ProgressUpdate
|
|
11
|
-
|
|
12
|
-
logger = get_logger(__name__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ProgressPublisher:
|
|
16
|
-
def __init__(self, settings: Settings | None = None) -> None:
|
|
17
|
-
self.settings = settings or Settings()
|
|
18
|
-
# Use the shared Redis connection pool
|
|
19
|
-
self._redis = get_redis_client()
|
|
20
|
-
|
|
21
|
-
async def publish_progress(self, job_id: str, update: ProgressUpdate) -> None:
|
|
22
|
-
"""Publish progress update to Redis and persist to database."""
|
|
23
|
-
channel = f"job:{job_id}:progress"
|
|
24
|
-
await self._persist_update(job_id, update)
|
|
25
|
-
json_data = update.model_dump_json()
|
|
26
|
-
logger.info(
|
|
27
|
-
"Publishing progress update to Redis",
|
|
28
|
-
job_id=job_id,
|
|
29
|
-
channel=channel,
|
|
30
|
-
status=update.status,
|
|
31
|
-
progress=update.progress,
|
|
32
|
-
data_length=len(json_data),
|
|
33
|
-
)
|
|
34
|
-
await self._redis.publish(channel, json_data)
|
|
35
|
-
logger.debug("Progress update published successfully", job_id=job_id)
|
|
36
|
-
|
|
37
|
-
async def publish_only(self, job_id: str, update: ProgressUpdate) -> None:
|
|
38
|
-
"""Publish progress update to Redis without persisting to database.
|
|
39
|
-
|
|
40
|
-
Use this when the database has already been updated separately,
|
|
41
|
-
e.g., after calling finalize_success in the repository.
|
|
42
|
-
"""
|
|
43
|
-
channel = f"job:{job_id}:progress"
|
|
44
|
-
json_data = update.model_dump_json()
|
|
45
|
-
logger.info(
|
|
46
|
-
"Publishing progress update to Redis (no DB persist)",
|
|
47
|
-
job_id=job_id,
|
|
48
|
-
channel=channel,
|
|
49
|
-
status=update.status,
|
|
50
|
-
progress=update.progress,
|
|
51
|
-
data_length=len(json_data),
|
|
52
|
-
)
|
|
53
|
-
await self._redis.publish(channel, json_data)
|
|
54
|
-
logger.debug("Progress update published successfully", job_id=job_id)
|
|
55
|
-
|
|
56
|
-
async def _persist_update(self, job_id: str, update: ProgressUpdate) -> None:
|
|
57
|
-
async with get_async_session() as session:
|
|
58
|
-
await jobs_repo.update_progress(
|
|
59
|
-
session,
|
|
60
|
-
generation_id=job_id,
|
|
61
|
-
status=update.status,
|
|
62
|
-
progress=(update.progress * 100 if update.progress <= 1.0 else update.progress),
|
|
63
|
-
error_message=update.message if update.status == "failed" else None,
|
|
64
|
-
)
|
|
File without changes
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"""Centralized Redis connection pool management.
|
|
2
|
-
|
|
3
|
-
This module provides a singleton Redis connection pool that can be shared
|
|
4
|
-
across the application to reduce connection overhead and improve performance.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import redis.asyncio as redis
|
|
10
|
-
from redis.asyncio.connection import ConnectionPool
|
|
11
|
-
|
|
12
|
-
from .config import Settings
|
|
13
|
-
from .logging import get_logger
|
|
14
|
-
|
|
15
|
-
logger = get_logger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class RedisPoolManager:
|
|
19
|
-
"""Singleton manager for Redis connection pool."""
|
|
20
|
-
|
|
21
|
-
_instance: RedisPoolManager | None = None
|
|
22
|
-
_pool: ConnectionPool | None = None
|
|
23
|
-
_client: redis.Redis | None = None
|
|
24
|
-
|
|
25
|
-
def __new__(cls) -> RedisPoolManager:
|
|
26
|
-
if cls._instance is None:
|
|
27
|
-
cls._instance = super().__new__(cls)
|
|
28
|
-
return cls._instance
|
|
29
|
-
|
|
30
|
-
def __init__(self):
|
|
31
|
-
"""Initialize the Redis pool manager."""
|
|
32
|
-
if self._pool is None:
|
|
33
|
-
settings = Settings()
|
|
34
|
-
|
|
35
|
-
# Create connection pool with sensible defaults
|
|
36
|
-
# These can be tuned based on your application's needs
|
|
37
|
-
self._pool = redis.ConnectionPool.from_url(
|
|
38
|
-
settings.redis_url,
|
|
39
|
-
decode_responses=True,
|
|
40
|
-
max_connections=50, # Maximum number of connections
|
|
41
|
-
socket_connect_timeout=5, # Connection timeout in seconds
|
|
42
|
-
socket_timeout=5, # Socket timeout in seconds
|
|
43
|
-
retry_on_timeout=True, # Retry on timeout
|
|
44
|
-
health_check_interval=30, # Health check every 30 seconds
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# Create Redis client using the pool
|
|
48
|
-
self._client = redis.Redis(connection_pool=self._pool)
|
|
49
|
-
|
|
50
|
-
logger.info(
|
|
51
|
-
"Redis connection pool initialized with max_connections=50, "
|
|
52
|
-
"health_check_interval=30s"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def client(self) -> redis.Redis:
|
|
57
|
-
"""Get the Redis client with connection pooling."""
|
|
58
|
-
if self._client is None:
|
|
59
|
-
raise RuntimeError("Redis pool not initialized")
|
|
60
|
-
return self._client
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def pool(self) -> ConnectionPool:
|
|
64
|
-
"""Get the underlying connection pool."""
|
|
65
|
-
if self._pool is None:
|
|
66
|
-
raise RuntimeError("Redis pool not initialized")
|
|
67
|
-
return self._pool
|
|
68
|
-
|
|
69
|
-
async def close(self):
|
|
70
|
-
"""Close the Redis connection pool."""
|
|
71
|
-
if self._client:
|
|
72
|
-
await self._client.close()
|
|
73
|
-
logger.info("Redis client closed")
|
|
74
|
-
if self._pool:
|
|
75
|
-
await self._pool.disconnect()
|
|
76
|
-
logger.info("Redis connection pool disconnected")
|
|
77
|
-
|
|
78
|
-
async def health_check(self) -> bool:
|
|
79
|
-
"""Check if Redis connection is healthy."""
|
|
80
|
-
try:
|
|
81
|
-
if self._client is None:
|
|
82
|
-
logger.error("Redis client not initialized")
|
|
83
|
-
return False
|
|
84
|
-
await self._client.ping()
|
|
85
|
-
return True
|
|
86
|
-
except Exception as e:
|
|
87
|
-
logger.error(f"Redis health check failed: {e}")
|
|
88
|
-
return False
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Global instance
|
|
92
|
-
_redis_pool_manager = RedisPoolManager()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def get_redis_client() -> redis.Redis:
|
|
96
|
-
"""Get a Redis client with connection pooling.
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
Redis client instance with connection pooling enabled.
|
|
100
|
-
"""
|
|
101
|
-
return _redis_pool_manager.client
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
async def close_redis_pool():
|
|
105
|
-
"""Close the Redis connection pool.
|
|
106
|
-
|
|
107
|
-
Call this during application shutdown to cleanly close connections.
|
|
108
|
-
"""
|
|
109
|
-
await _redis_pool_manager.close()
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
async def check_redis_health() -> bool:
|
|
113
|
-
"""Check if Redis is healthy and accessible.
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
True if Redis is healthy, False otherwise.
|
|
117
|
-
"""
|
|
118
|
-
return await _redis_pool_manager.health_check()
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"""Storage system for Boards artifacts.
|
|
2
|
-
|
|
3
|
-
This module provides a pluggable storage architecture that supports:
|
|
4
|
-
- Local filesystem storage for development
|
|
5
|
-
- Supabase storage with auth integration
|
|
6
|
-
- S3 storage for enterprise deployments
|
|
7
|
-
- Custom storage providers via plugin system
|
|
8
|
-
|
|
9
|
-
Main components:
|
|
10
|
-
- StorageProvider: Abstract base class for storage implementations
|
|
11
|
-
- StorageManager: Central coordinator for routing and operations
|
|
12
|
-
- ArtifactReference: Metadata about stored artifacts
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from .base import (
|
|
16
|
-
ArtifactReference,
|
|
17
|
-
SecurityException,
|
|
18
|
-
StorageConfig,
|
|
19
|
-
StorageException,
|
|
20
|
-
StorageManager,
|
|
21
|
-
StorageProvider,
|
|
22
|
-
ValidationException,
|
|
23
|
-
)
|
|
24
|
-
from .config import (
|
|
25
|
-
create_example_config,
|
|
26
|
-
load_storage_config,
|
|
27
|
-
)
|
|
28
|
-
from .factory import (
|
|
29
|
-
create_development_storage,
|
|
30
|
-
create_storage_manager,
|
|
31
|
-
create_storage_provider,
|
|
32
|
-
get_storage_config,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
__all__ = [
|
|
36
|
-
# Base classes and exceptions
|
|
37
|
-
"StorageProvider",
|
|
38
|
-
"StorageManager",
|
|
39
|
-
"StorageConfig",
|
|
40
|
-
"ArtifactReference",
|
|
41
|
-
"StorageException",
|
|
42
|
-
"SecurityException",
|
|
43
|
-
"ValidationException",
|
|
44
|
-
# Factory functions
|
|
45
|
-
"create_storage_provider",
|
|
46
|
-
"create_storage_manager",
|
|
47
|
-
"create_development_storage",
|
|
48
|
-
"get_storage_config",
|
|
49
|
-
# Configuration
|
|
50
|
-
"load_storage_config",
|
|
51
|
-
"create_example_config",
|
|
52
|
-
]
|