@weirdfingers/baseboards 0.9.5 → 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 +561 -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,155 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Storage endpoints for file uploads and management
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter, HTTPException
|
|
8
|
-
from fastapi.responses import FileResponse
|
|
9
|
-
|
|
10
|
-
from ...logging import get_logger
|
|
11
|
-
from ...storage.factory import create_storage_manager
|
|
12
|
-
from ...storage.implementations.local import LocalStorageProvider
|
|
13
|
-
|
|
14
|
-
logger = get_logger(__name__)
|
|
15
|
-
router = APIRouter()
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@router.get("/status")
|
|
19
|
-
async def storage_status():
|
|
20
|
-
"""Storage status endpoint."""
|
|
21
|
-
return {"status": "Storage endpoint ready"}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _get_extension_from_content_type(content_type: str) -> str:
|
|
25
|
-
"""Get file extension from content type.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
content_type: MIME type (e.g., 'video/mp4', 'image/png', 'audio/mpeg')
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
File extension with dot (e.g., '.mp4', '.png', '.mp3')
|
|
32
|
-
"""
|
|
33
|
-
# Map common content types to extensions
|
|
34
|
-
content_type_map = {
|
|
35
|
-
# Video
|
|
36
|
-
"video/mp4": ".mp4",
|
|
37
|
-
"video/webm": ".webm",
|
|
38
|
-
"video/quicktime": ".mov",
|
|
39
|
-
"video/x-msvideo": ".avi",
|
|
40
|
-
# Image
|
|
41
|
-
"image/png": ".png",
|
|
42
|
-
"image/jpeg": ".jpg",
|
|
43
|
-
"image/jpg": ".jpg",
|
|
44
|
-
"image/webp": ".webp",
|
|
45
|
-
"image/gif": ".gif",
|
|
46
|
-
# Audio
|
|
47
|
-
"audio/mpeg": ".mp3",
|
|
48
|
-
"audio/mp3": ".mp3",
|
|
49
|
-
"audio/wav": ".wav",
|
|
50
|
-
"audio/ogg": ".ogg",
|
|
51
|
-
"audio/aac": ".aac",
|
|
52
|
-
# Text
|
|
53
|
-
"text/plain": ".txt",
|
|
54
|
-
"text/html": ".html",
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return content_type_map.get(content_type.lower(), "")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@router.get("/{full_path:path}")
|
|
61
|
-
async def serve_file(full_path: str, download: bool = False, filename: str | None = None):
|
|
62
|
-
"""Serve a file from local storage.
|
|
63
|
-
|
|
64
|
-
This endpoint serves files that were uploaded to local storage.
|
|
65
|
-
The full_path includes the tenant_id/artifact_type/board_id/artifact_id/variant structure.
|
|
66
|
-
|
|
67
|
-
Args:
|
|
68
|
-
full_path: Path to the file in storage
|
|
69
|
-
download: If True, force download with Content-Disposition: attachment
|
|
70
|
-
filename: Optional custom filename (without extension) to use for download
|
|
71
|
-
"""
|
|
72
|
-
try:
|
|
73
|
-
logger.info("Serving file", full_path=full_path, download=download, filename=filename)
|
|
74
|
-
|
|
75
|
-
# Create storage manager to get the configured local storage path
|
|
76
|
-
storage_manager = create_storage_manager()
|
|
77
|
-
|
|
78
|
-
# Get the local provider (assumes 'local' is the provider name)
|
|
79
|
-
local_provider = storage_manager.providers.get("local")
|
|
80
|
-
if not local_provider:
|
|
81
|
-
raise HTTPException(status_code=500, detail="Local storage provider not configured")
|
|
82
|
-
|
|
83
|
-
# Type check: ensure it's a LocalStorageProvider
|
|
84
|
-
# This endpoint only serves local files; cloud providers return direct URLs
|
|
85
|
-
if not isinstance(local_provider, LocalStorageProvider):
|
|
86
|
-
raise HTTPException(
|
|
87
|
-
status_code=500,
|
|
88
|
-
detail="Storage provider does not support local file serving",
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
base_path = local_provider.base_path
|
|
92
|
-
file_path = Path(base_path) / full_path
|
|
93
|
-
|
|
94
|
-
# Security check: ensure the resolved path is within base_path
|
|
95
|
-
try:
|
|
96
|
-
file_path.resolve().relative_to(Path(base_path).resolve())
|
|
97
|
-
except ValueError as e:
|
|
98
|
-
logger.warning("Path traversal attempt detected", requested_path=full_path)
|
|
99
|
-
raise HTTPException(status_code=403, detail="Access denied") from e
|
|
100
|
-
|
|
101
|
-
# Check if file exists
|
|
102
|
-
if not file_path.exists():
|
|
103
|
-
logger.warning("File not found", path=str(file_path))
|
|
104
|
-
raise HTTPException(status_code=404, detail="File not found")
|
|
105
|
-
|
|
106
|
-
if not file_path.is_file():
|
|
107
|
-
raise HTTPException(status_code=400, detail="Path is not a file")
|
|
108
|
-
|
|
109
|
-
# Determine the proper filename with extension
|
|
110
|
-
base_filename = filename if filename else file_path.stem
|
|
111
|
-
final_filename = file_path.name
|
|
112
|
-
has_extension = False
|
|
113
|
-
|
|
114
|
-
# Try to get metadata from storage to determine content type and proper extension
|
|
115
|
-
try:
|
|
116
|
-
metadata = await local_provider.get_metadata(full_path)
|
|
117
|
-
content_type = metadata.get("content_type")
|
|
118
|
-
|
|
119
|
-
if content_type:
|
|
120
|
-
extension = _get_extension_from_content_type(content_type)
|
|
121
|
-
if extension:
|
|
122
|
-
# Use custom filename if provided, otherwise use file stem
|
|
123
|
-
final_filename = f"{base_filename}{extension}"
|
|
124
|
-
has_extension = True
|
|
125
|
-
logger.info(
|
|
126
|
-
"Determined filename from storage metadata",
|
|
127
|
-
original=file_path.name,
|
|
128
|
-
new_filename=final_filename,
|
|
129
|
-
content_type=content_type,
|
|
130
|
-
custom_filename=filename,
|
|
131
|
-
)
|
|
132
|
-
except Exception as e:
|
|
133
|
-
# Log but don't fail if we can't get metadata
|
|
134
|
-
logger.warning("Failed to get storage metadata", path=full_path, error=str(e))
|
|
135
|
-
|
|
136
|
-
# Serve the file with proper filename
|
|
137
|
-
# Only set Content-Disposition if:
|
|
138
|
-
# 1. Download is explicitly requested, OR
|
|
139
|
-
# 2. We have a proper extension from metadata
|
|
140
|
-
headers = {}
|
|
141
|
-
if download:
|
|
142
|
-
# Force download with attachment
|
|
143
|
-
headers["Content-Disposition"] = f'attachment; filename="{final_filename}"'
|
|
144
|
-
elif has_extension:
|
|
145
|
-
# We have proper metadata, suggest filename but allow inline preview
|
|
146
|
-
headers["Content-Disposition"] = f'inline; filename="{final_filename}"'
|
|
147
|
-
# else: No Content-Disposition header - let browser decide based on content-type
|
|
148
|
-
|
|
149
|
-
return FileResponse(file_path, filename=final_filename, headers=headers)
|
|
150
|
-
|
|
151
|
-
except HTTPException:
|
|
152
|
-
raise
|
|
153
|
-
except Exception as e:
|
|
154
|
-
logger.error("Error serving file", path=full_path, error=str(e))
|
|
155
|
-
raise HTTPException(status_code=500, detail="Internal server error") from e
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Self-service tenant registration endpoints.
|
|
3
|
-
|
|
4
|
-
This module provides endpoints for organizations to register new tenants
|
|
5
|
-
in multi-tenant mode, with optional approval workflows.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from fastapi import APIRouter, Depends, HTTPException
|
|
13
|
-
from pydantic import BaseModel, Field, field_validator
|
|
14
|
-
|
|
15
|
-
from ...auth.context import AuthContext
|
|
16
|
-
from ...auth.middleware import get_auth_context
|
|
17
|
-
from ...config import settings
|
|
18
|
-
from ...database.connection import get_async_session
|
|
19
|
-
from ...database.seed_data import ensure_tenant, seed_tenant_with_data
|
|
20
|
-
from ...logging import get_logger
|
|
21
|
-
|
|
22
|
-
logger = get_logger(__name__)
|
|
23
|
-
|
|
24
|
-
router = APIRouter()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class TenantRegistrationRequest(BaseModel):
|
|
28
|
-
"""Request model for self-service tenant registration."""
|
|
29
|
-
|
|
30
|
-
organization_name: str = Field(
|
|
31
|
-
..., min_length=1, max_length=255, description="Organization name for the new tenant"
|
|
32
|
-
)
|
|
33
|
-
organization_slug: str | None = Field(
|
|
34
|
-
None,
|
|
35
|
-
min_length=1,
|
|
36
|
-
max_length=255,
|
|
37
|
-
pattern=r"^[a-z0-9-]+$",
|
|
38
|
-
description="Desired tenant slug (auto-generated if not provided)",
|
|
39
|
-
)
|
|
40
|
-
admin_email: str = Field(..., description="Email of the organization administrator")
|
|
41
|
-
admin_name: str | None = Field(None, description="Full name of the organization administrator")
|
|
42
|
-
use_case: str | None = Field(
|
|
43
|
-
None, max_length=500, description="Brief description of intended use case"
|
|
44
|
-
)
|
|
45
|
-
organization_size: str | None = Field(
|
|
46
|
-
None, description="Size of organization (small, medium, large, enterprise)"
|
|
47
|
-
)
|
|
48
|
-
metadata: dict[str, Any] = Field(
|
|
49
|
-
default_factory=dict, description="Additional registration metadata"
|
|
50
|
-
)
|
|
51
|
-
include_sample_data: bool = Field(
|
|
52
|
-
default=True, description="Whether to include sample boards and data"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
@field_validator("admin_email")
|
|
56
|
-
@classmethod
|
|
57
|
-
def validate_email(cls, v: str) -> str:
|
|
58
|
-
"""Basic email validation."""
|
|
59
|
-
import re
|
|
60
|
-
|
|
61
|
-
if not re.match(r"^[^@]+@[^@]+\.[^@]+$", v):
|
|
62
|
-
raise ValueError("Invalid email format")
|
|
63
|
-
return v.lower()
|
|
64
|
-
|
|
65
|
-
@field_validator("organization_size")
|
|
66
|
-
@classmethod
|
|
67
|
-
def validate_organization_size(cls, v: str | None) -> str | None:
|
|
68
|
-
"""Validate organization size values."""
|
|
69
|
-
if v is not None:
|
|
70
|
-
valid_sizes = {"small", "medium", "large", "enterprise"}
|
|
71
|
-
if v.lower() not in valid_sizes:
|
|
72
|
-
raise ValueError(f"Organization size must be one of: {', '.join(valid_sizes)}")
|
|
73
|
-
return v.lower() if v else None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class TenantRegistrationResponse(BaseModel):
|
|
77
|
-
"""Response model for tenant registration."""
|
|
78
|
-
|
|
79
|
-
tenant_id: str = Field(..., description="UUID of the registered tenant")
|
|
80
|
-
organization_name: str = Field(..., description="Organization name")
|
|
81
|
-
tenant_slug: str = Field(..., description="Assigned tenant slug")
|
|
82
|
-
status: str = Field(..., description="Registration status")
|
|
83
|
-
admin_instructions: str = Field(..., description="Next steps for the administrator")
|
|
84
|
-
dashboard_url: str | None = Field(None, description="URL to access tenant dashboard")
|
|
85
|
-
api_access: dict[str, Any] = Field(..., description="API access information")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class TenantRegistrationStatus(BaseModel):
|
|
89
|
-
"""Model for checking registration status."""
|
|
90
|
-
|
|
91
|
-
enabled: bool = Field(..., description="Whether self-service registration is enabled")
|
|
92
|
-
requires_approval: bool = Field(..., description="Whether registrations require approval")
|
|
93
|
-
max_tenants_per_user: int | None = Field(None, description="Maximum tenants per user")
|
|
94
|
-
allowed_domains: list[str] | None = Field(None, description="Allowed email domains")
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@router.get("/registration/status", response_model=TenantRegistrationStatus)
|
|
98
|
-
async def get_registration_status() -> TenantRegistrationStatus:
|
|
99
|
-
"""
|
|
100
|
-
Get the current tenant registration status and configuration.
|
|
101
|
-
"""
|
|
102
|
-
return TenantRegistrationStatus(
|
|
103
|
-
enabled=settings.multi_tenant_mode,
|
|
104
|
-
requires_approval=getattr(settings, "tenant_registration_requires_approval", False),
|
|
105
|
-
max_tenants_per_user=getattr(settings, "max_tenants_per_user", None),
|
|
106
|
-
allowed_domains=getattr(settings, "tenant_registration_allowed_domains", None),
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@router.post("/register", response_model=TenantRegistrationResponse)
|
|
111
|
-
async def register_tenant(
|
|
112
|
-
request: TenantRegistrationRequest,
|
|
113
|
-
auth_context: AuthContext = Depends(get_auth_context),
|
|
114
|
-
) -> TenantRegistrationResponse:
|
|
115
|
-
"""
|
|
116
|
-
Register a new tenant for an organization.
|
|
117
|
-
|
|
118
|
-
This endpoint allows authenticated users to create new tenants for their
|
|
119
|
-
organizations. In multi-tenant mode, this enables self-service onboarding.
|
|
120
|
-
|
|
121
|
-
Requirements:
|
|
122
|
-
- Multi-tenant mode must be enabled
|
|
123
|
-
- User must be authenticated
|
|
124
|
-
- Organization slug must be unique
|
|
125
|
-
- Optional: email domain validation
|
|
126
|
-
- Optional: approval workflow
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
# Validate prerequisites
|
|
130
|
-
if not settings.multi_tenant_mode:
|
|
131
|
-
raise HTTPException(
|
|
132
|
-
status_code=400, detail="Tenant registration is only available in multi-tenant mode"
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if not auth_context.user_id:
|
|
136
|
-
raise HTTPException(
|
|
137
|
-
status_code=401, detail="Authentication required for tenant registration"
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
# Validate email domain if restrictions are configured
|
|
141
|
-
allowed_domains = getattr(settings, "tenant_registration_allowed_domains", None)
|
|
142
|
-
if allowed_domains:
|
|
143
|
-
admin_domain = request.admin_email.split("@")[1].lower()
|
|
144
|
-
if admin_domain not in allowed_domains:
|
|
145
|
-
raise HTTPException(
|
|
146
|
-
status_code=400,
|
|
147
|
-
detail=f"Email domain '{admin_domain}' is not allowed for registration",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
# Check if user has reached tenant limit
|
|
151
|
-
max_tenants = getattr(settings, "max_tenants_per_user", None)
|
|
152
|
-
if max_tenants:
|
|
153
|
-
# TODO: Implement tenant count check per user
|
|
154
|
-
logger.warning(
|
|
155
|
-
"Tenant limit check not implemented",
|
|
156
|
-
user_id=str(auth_context.user_id),
|
|
157
|
-
max_tenants=max_tenants,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
# Generate tenant slug if not provided
|
|
161
|
-
tenant_slug = request.organization_slug
|
|
162
|
-
if not tenant_slug:
|
|
163
|
-
tenant_slug = _generate_slug_from_name(request.organization_name)
|
|
164
|
-
|
|
165
|
-
logger.info(
|
|
166
|
-
"Processing tenant registration request",
|
|
167
|
-
organization_name=request.organization_name,
|
|
168
|
-
tenant_slug=tenant_slug,
|
|
169
|
-
admin_email=request.admin_email,
|
|
170
|
-
user_id=str(auth_context.user_id),
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
try:
|
|
174
|
-
async with get_async_session() as db:
|
|
175
|
-
# Create tenant with sample data
|
|
176
|
-
if request.include_sample_data:
|
|
177
|
-
tenant_id = await seed_tenant_with_data(
|
|
178
|
-
db,
|
|
179
|
-
tenant_name=request.organization_name,
|
|
180
|
-
tenant_slug=tenant_slug,
|
|
181
|
-
tenant_settings={
|
|
182
|
-
"admin_email": request.admin_email,
|
|
183
|
-
"admin_name": request.admin_name,
|
|
184
|
-
"use_case": request.use_case,
|
|
185
|
-
"organization_size": request.organization_size,
|
|
186
|
-
"registered_by": str(auth_context.user_id),
|
|
187
|
-
"registration_metadata": request.metadata,
|
|
188
|
-
},
|
|
189
|
-
include_sample_data=True,
|
|
190
|
-
)
|
|
191
|
-
else:
|
|
192
|
-
tenant_id = await ensure_tenant(
|
|
193
|
-
db,
|
|
194
|
-
name=request.organization_name,
|
|
195
|
-
slug=tenant_slug,
|
|
196
|
-
settings_dict={
|
|
197
|
-
"admin_email": request.admin_email,
|
|
198
|
-
"admin_name": request.admin_name,
|
|
199
|
-
"use_case": request.use_case,
|
|
200
|
-
"organization_size": request.organization_size,
|
|
201
|
-
"registered_by": str(auth_context.user_id),
|
|
202
|
-
"registration_metadata": request.metadata,
|
|
203
|
-
},
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
# Determine status based on approval requirements
|
|
207
|
-
requires_approval = getattr(settings, "tenant_registration_requires_approval", False)
|
|
208
|
-
status = "pending_approval" if requires_approval else "active"
|
|
209
|
-
|
|
210
|
-
# Generate dashboard URL if available
|
|
211
|
-
dashboard_url = _generate_dashboard_url(tenant_slug)
|
|
212
|
-
|
|
213
|
-
logger.info(
|
|
214
|
-
"Tenant registration completed",
|
|
215
|
-
tenant_id=str(tenant_id),
|
|
216
|
-
tenant_slug=tenant_slug,
|
|
217
|
-
status=status,
|
|
218
|
-
admin_email=request.admin_email,
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
return TenantRegistrationResponse(
|
|
222
|
-
tenant_id=str(tenant_id),
|
|
223
|
-
organization_name=request.organization_name,
|
|
224
|
-
tenant_slug=tenant_slug,
|
|
225
|
-
status=status,
|
|
226
|
-
admin_instructions=_generate_admin_instructions(status, tenant_slug),
|
|
227
|
-
dashboard_url=dashboard_url,
|
|
228
|
-
api_access={
|
|
229
|
-
"tenant_header": f"X-Tenant: {tenant_slug}",
|
|
230
|
-
"graphql_endpoint": "/graphql",
|
|
231
|
-
"api_base_url": "/api",
|
|
232
|
-
"authentication_required": True,
|
|
233
|
-
},
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
except Exception as e:
|
|
237
|
-
logger.error(
|
|
238
|
-
"Tenant registration failed",
|
|
239
|
-
error=str(e),
|
|
240
|
-
organization_name=request.organization_name,
|
|
241
|
-
tenant_slug=tenant_slug,
|
|
242
|
-
)
|
|
243
|
-
raise HTTPException(status_code=500, detail=f"Failed to register tenant: {str(e)}") from e
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _generate_slug_from_name(organization_name: str) -> str:
|
|
247
|
-
"""Generate a URL-safe slug from organization name."""
|
|
248
|
-
import re
|
|
249
|
-
import uuid
|
|
250
|
-
|
|
251
|
-
# Basic normalization
|
|
252
|
-
slug = organization_name.lower().strip()
|
|
253
|
-
|
|
254
|
-
# Replace spaces and special characters with hyphens
|
|
255
|
-
slug = re.sub(r"[^a-z0-9]+", "-", slug)
|
|
256
|
-
|
|
257
|
-
# Remove multiple consecutive hyphens
|
|
258
|
-
slug = re.sub(r"-+", "-", slug)
|
|
259
|
-
|
|
260
|
-
# Remove leading/trailing hyphens
|
|
261
|
-
slug = slug.strip("-")
|
|
262
|
-
|
|
263
|
-
# Ensure it's not empty
|
|
264
|
-
if not slug or len(slug) < 3:
|
|
265
|
-
slug = f"org-{uuid.uuid4().hex[:8]}"
|
|
266
|
-
|
|
267
|
-
# Ensure it's not too long
|
|
268
|
-
if len(slug) > 50:
|
|
269
|
-
slug = slug[:47] + f"-{uuid.uuid4().hex[:2]}"
|
|
270
|
-
|
|
271
|
-
return slug
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
def _generate_dashboard_url(tenant_slug: str) -> str | None:
|
|
275
|
-
"""Generate dashboard URL for the tenant."""
|
|
276
|
-
# This would typically point to your frontend application
|
|
277
|
-
frontend_base_url = getattr(settings, "frontend_base_url", None)
|
|
278
|
-
if frontend_base_url:
|
|
279
|
-
return f"{frontend_base_url}/?tenant={tenant_slug}"
|
|
280
|
-
return None
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def _generate_admin_instructions(status: str, tenant_slug: str) -> str:
|
|
284
|
-
"""Generate setup instructions for the tenant administrator."""
|
|
285
|
-
if status == "pending_approval":
|
|
286
|
-
return (
|
|
287
|
-
"Your tenant registration is pending approval. "
|
|
288
|
-
"You will receive an email notification when your tenant is activated. "
|
|
289
|
-
f"Your tenant slug is '{tenant_slug}' - save this for future reference."
|
|
290
|
-
)
|
|
291
|
-
else:
|
|
292
|
-
return (
|
|
293
|
-
f"Your tenant '{tenant_slug}' is ready to use! "
|
|
294
|
-
"Include the X-Tenant header in all API requests. "
|
|
295
|
-
"Visit the dashboard to get started with boards and content generation."
|
|
296
|
-
)
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
"""File upload endpoints for artifact uploads."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from typing import Annotated
|
|
5
|
-
from uuid import UUID
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
|
8
|
-
|
|
9
|
-
from ...auth import get_auth_context
|
|
10
|
-
from ...auth.context import AuthContext
|
|
11
|
-
from ...config import settings
|
|
12
|
-
from ...logging import get_logger
|
|
13
|
-
|
|
14
|
-
router = APIRouter(prefix="/uploads", tags=["uploads"])
|
|
15
|
-
logger = get_logger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@router.post("/artifact")
|
|
19
|
-
async def upload_artifact_file(
|
|
20
|
-
board_id: Annotated[str, Form()],
|
|
21
|
-
artifact_type: Annotated[str, Form()], # image, video, audio, text
|
|
22
|
-
file: UploadFile = File(...),
|
|
23
|
-
user_description: Annotated[str | None, Form()] = None,
|
|
24
|
-
parent_generation_id: Annotated[str | None, Form()] = None,
|
|
25
|
-
auth_context: AuthContext = Depends(get_auth_context),
|
|
26
|
-
) -> dict:
|
|
27
|
-
"""
|
|
28
|
-
Upload artifact file (synchronous).
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
board_id: UUID of the board to upload to
|
|
32
|
-
artifact_type: Type of artifact (image, video, audio, text)
|
|
33
|
-
file: The file to upload
|
|
34
|
-
user_description: Optional description provided by user
|
|
35
|
-
parent_generation_id: Optional parent generation UUID
|
|
36
|
-
auth_context: Authentication context
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
Generation object as JSON
|
|
40
|
-
|
|
41
|
-
Raises:
|
|
42
|
-
HTTPException: If validation fails or upload errors occur
|
|
43
|
-
"""
|
|
44
|
-
from ...graphql.resolvers.upload import upload_artifact_from_file
|
|
45
|
-
|
|
46
|
-
# Validate authentication
|
|
47
|
-
if not auth_context.is_authenticated or not auth_context.user_id:
|
|
48
|
-
raise HTTPException(
|
|
49
|
-
status_code=401,
|
|
50
|
-
detail="Authentication required",
|
|
51
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
# Validate artifact type
|
|
55
|
-
valid_types = {"image", "video", "audio", "text"}
|
|
56
|
-
if artifact_type not in valid_types:
|
|
57
|
-
raise HTTPException(
|
|
58
|
-
status_code=400,
|
|
59
|
-
detail=f"Invalid artifact_type. Must be one of: {', '.join(valid_types)}",
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
# Read file content
|
|
63
|
-
try:
|
|
64
|
-
content = await file.read()
|
|
65
|
-
except Exception as e:
|
|
66
|
-
logger.error("Failed to read uploaded file", error=str(e), filename=file.filename)
|
|
67
|
-
raise HTTPException(
|
|
68
|
-
status_code=400,
|
|
69
|
-
detail="Failed to read uploaded file",
|
|
70
|
-
) from e
|
|
71
|
-
|
|
72
|
-
# Validate file size
|
|
73
|
-
if len(content) > settings.max_upload_size:
|
|
74
|
-
raise HTTPException(
|
|
75
|
-
status_code=413,
|
|
76
|
-
detail=(
|
|
77
|
-
f"File size {len(content)} bytes exceeds maximum allowed size "
|
|
78
|
-
f"of {settings.max_upload_size} bytes"
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
# Validate extension
|
|
83
|
-
file_ext = os.path.splitext(file.filename or "")[1].lower()
|
|
84
|
-
if file_ext and file_ext not in settings.allowed_upload_extensions:
|
|
85
|
-
allowed_exts = ", ".join(settings.allowed_upload_extensions)
|
|
86
|
-
raise HTTPException(
|
|
87
|
-
status_code=400,
|
|
88
|
-
detail=(
|
|
89
|
-
f"File extension '{file_ext}' is not allowed. Allowed extensions: {allowed_exts}"
|
|
90
|
-
),
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# Parse UUIDs
|
|
94
|
-
try:
|
|
95
|
-
board_uuid = UUID(board_id)
|
|
96
|
-
parent_uuid = UUID(parent_generation_id) if parent_generation_id else None
|
|
97
|
-
except ValueError as e:
|
|
98
|
-
logger.warning("Invalid UUID provided", board_id=board_id, error=str(e))
|
|
99
|
-
raise HTTPException(
|
|
100
|
-
status_code=400,
|
|
101
|
-
detail="Invalid board_id or parent_generation_id format",
|
|
102
|
-
) from e
|
|
103
|
-
|
|
104
|
-
# Call resolver
|
|
105
|
-
try:
|
|
106
|
-
generation = await upload_artifact_from_file(
|
|
107
|
-
auth_context=auth_context,
|
|
108
|
-
board_id=board_uuid,
|
|
109
|
-
artifact_type=artifact_type,
|
|
110
|
-
file_content=content,
|
|
111
|
-
filename=file.filename,
|
|
112
|
-
content_type=file.content_type,
|
|
113
|
-
user_description=user_description,
|
|
114
|
-
parent_generation_id=parent_uuid,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
logger.info(
|
|
118
|
-
"File upload successful",
|
|
119
|
-
generation_id=str(generation.id),
|
|
120
|
-
artifact_type=artifact_type,
|
|
121
|
-
file_size=len(content),
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
"id": str(generation.id),
|
|
126
|
-
"status": generation.status.value,
|
|
127
|
-
"storageUrl": generation.storage_url,
|
|
128
|
-
"thumbnailUrl": generation.thumbnail_url,
|
|
129
|
-
"artifactType": generation.artifact_type.value,
|
|
130
|
-
"generatorName": generation.generator_name,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
except RuntimeError as e:
|
|
134
|
-
# These are expected errors (permission denied, board not found, etc.)
|
|
135
|
-
# Pass through the message since these are safe, user-facing errors
|
|
136
|
-
logger.warning("Upload failed", error=str(e))
|
|
137
|
-
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
138
|
-
except Exception as e:
|
|
139
|
-
# Unexpected errors - don't expose internal details
|
|
140
|
-
logger.error(
|
|
141
|
-
"Unexpected error during upload",
|
|
142
|
-
error=str(e),
|
|
143
|
-
board_id=board_id,
|
|
144
|
-
artifact_type=artifact_type,
|
|
145
|
-
)
|
|
146
|
-
raise HTTPException(
|
|
147
|
-
status_code=500,
|
|
148
|
-
detail="An unexpected error occurred during upload",
|
|
149
|
-
) from e
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Webhook endpoints for external service integrations
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from fastapi import APIRouter
|
|
6
|
-
|
|
7
|
-
router = APIRouter()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@router.get("/status")
|
|
11
|
-
async def webhook_status():
|
|
12
|
-
"""Webhook status endpoint."""
|
|
13
|
-
return {"status": "Webhook endpoint ready"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""Authentication and authorization system for Boards."""
|
|
2
|
-
|
|
3
|
-
from .adapters.base import AuthAdapter, Principal
|
|
4
|
-
from .context import AuthContext
|
|
5
|
-
from .factory import get_auth_adapter
|
|
6
|
-
from .middleware import get_auth_context, get_auth_context_optional
|
|
7
|
-
|
|
8
|
-
__all__ = [
|
|
9
|
-
"AuthAdapter",
|
|
10
|
-
"Principal",
|
|
11
|
-
"AuthContext",
|
|
12
|
-
"get_auth_context",
|
|
13
|
-
"get_auth_context_optional",
|
|
14
|
-
"get_auth_adapter",
|
|
15
|
-
]
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Authentication adapters for different providers."""
|
|
2
|
-
|
|
3
|
-
from .auth0 import Auth0OIDCAdapter
|
|
4
|
-
from .base import AuthAdapter, Principal
|
|
5
|
-
from .clerk import ClerkAuthAdapter
|
|
6
|
-
from .jwt import JWTAuthAdapter
|
|
7
|
-
from .none import NoAuthAdapter
|
|
8
|
-
from .oidc import OIDCAdapter
|
|
9
|
-
|
|
10
|
-
# Always available adapters
|
|
11
|
-
__all__ = [
|
|
12
|
-
"AuthAdapter",
|
|
13
|
-
"Principal",
|
|
14
|
-
"JWTAuthAdapter",
|
|
15
|
-
"NoAuthAdapter",
|
|
16
|
-
"ClerkAuthAdapter",
|
|
17
|
-
"Auth0OIDCAdapter",
|
|
18
|
-
"OIDCAdapter",
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
# Optional auth providers - imported conditionally to avoid import errors
|
|
22
|
-
try:
|
|
23
|
-
from .supabase import SupabaseAuthAdapter
|
|
24
|
-
|
|
25
|
-
__all__.append("SupabaseAuthAdapter")
|
|
26
|
-
except ImportError:
|
|
27
|
-
pass
|