@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,292 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Database connection management
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import threading
|
|
7
|
-
from collections.abc import AsyncGenerator, Generator
|
|
8
|
-
from contextlib import asynccontextmanager, contextmanager
|
|
9
|
-
|
|
10
|
-
from sqlalchemy import create_engine, text
|
|
11
|
-
from sqlalchemy.ext.asyncio import (
|
|
12
|
-
AsyncEngine,
|
|
13
|
-
AsyncSession,
|
|
14
|
-
async_sessionmaker,
|
|
15
|
-
create_async_engine,
|
|
16
|
-
)
|
|
17
|
-
from sqlalchemy.orm import Session, sessionmaker
|
|
18
|
-
|
|
19
|
-
from ..config import settings
|
|
20
|
-
from ..logging import get_logger
|
|
21
|
-
|
|
22
|
-
logger = get_logger(__name__)
|
|
23
|
-
|
|
24
|
-
# Global shared connection pools (proper FastAPI pattern)
|
|
25
|
-
_engine = None
|
|
26
|
-
_session_local = None
|
|
27
|
-
_sync_initialized = False
|
|
28
|
-
_sync_init_lock = threading.Lock()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class AsyncDBContext(threading.local):
|
|
32
|
-
engine: AsyncEngine | None
|
|
33
|
-
initialized: bool
|
|
34
|
-
session_local: async_sessionmaker[AsyncSession] | None
|
|
35
|
-
lock: threading.Lock
|
|
36
|
-
|
|
37
|
-
def __init__(self):
|
|
38
|
-
self.engine = None
|
|
39
|
-
self.initialized = False
|
|
40
|
-
self.session_local = None
|
|
41
|
-
self.lock = threading.Lock() # Per-thread lock for async initialization
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
_async_db_ctx = AsyncDBContext()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def get_database_url() -> str:
|
|
48
|
-
"""Get database URL, checking environment variables first for test compatibility."""
|
|
49
|
-
# Always check environment first (for tests), then fall back to settings
|
|
50
|
-
db_url = os.getenv("BOARDS_DATABASE_URL")
|
|
51
|
-
if not db_url:
|
|
52
|
-
# For tests that set env vars after settings are loaded
|
|
53
|
-
if "BOARDS_DATABASE_URL" in os.environ:
|
|
54
|
-
db_url = os.environ["BOARDS_DATABASE_URL"]
|
|
55
|
-
else:
|
|
56
|
-
db_url = settings.database_url
|
|
57
|
-
return db_url
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def reset_database():
|
|
61
|
-
"""Reset database connections (for tests)."""
|
|
62
|
-
global _engine, _session_local, _sync_initialized
|
|
63
|
-
|
|
64
|
-
# Dispose of sync engine if it exists
|
|
65
|
-
if _engine is not None:
|
|
66
|
-
_engine.dispose()
|
|
67
|
-
|
|
68
|
-
_engine = None
|
|
69
|
-
_session_local = None
|
|
70
|
-
_sync_initialized = False
|
|
71
|
-
|
|
72
|
-
# Reset async context for current thread
|
|
73
|
-
# Note: async engine disposal must be done with await, so we just clear the reference
|
|
74
|
-
# The engine will be garbage collected when no sessions reference it
|
|
75
|
-
_async_db_ctx.engine = None
|
|
76
|
-
_async_db_ctx.session_local = None
|
|
77
|
-
_async_db_ctx.initialized = False
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
async def test_database_connection() -> tuple[bool, str | None]:
|
|
81
|
-
"""
|
|
82
|
-
Test the database connection and return helpful error messages.
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
tuple: (success: bool, error_message: str | None)
|
|
86
|
-
"""
|
|
87
|
-
if _async_db_ctx.engine is None:
|
|
88
|
-
return False, "Database engine not initialized"
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
async with _async_db_ctx.engine.connect() as conn:
|
|
92
|
-
await conn.execute(text("SELECT 1"))
|
|
93
|
-
return True, None
|
|
94
|
-
except Exception as e:
|
|
95
|
-
error_str = str(e)
|
|
96
|
-
error_type = type(e).__name__
|
|
97
|
-
|
|
98
|
-
# Provide helpful error messages based on the error type
|
|
99
|
-
if "does not exist" in error_str and "role" in error_str:
|
|
100
|
-
# This is the confusing error - could be database server not running
|
|
101
|
-
db_url = get_database_url()
|
|
102
|
-
# Extract database name from URL
|
|
103
|
-
db_name = db_url.split("/")[-1].split("?")[0]
|
|
104
|
-
return False, (
|
|
105
|
-
f"Cannot connect to database: {error_str}\n"
|
|
106
|
-
f"This usually means:\n"
|
|
107
|
-
f" 1. The database server is not running\n"
|
|
108
|
-
f" 2. The database '{db_name}' doesn't exist\n"
|
|
109
|
-
f" 3. The database user/role doesn't exist\n"
|
|
110
|
-
f"Please check your database connection and run migrations if needed."
|
|
111
|
-
)
|
|
112
|
-
elif "Connection refused" in error_str or "could not connect" in error_str:
|
|
113
|
-
return False, (
|
|
114
|
-
f"Cannot connect to database server: {error_str}\n"
|
|
115
|
-
f"The database server appears to be down or unreachable.\n"
|
|
116
|
-
f"Please check that PostgreSQL is running and accessible."
|
|
117
|
-
)
|
|
118
|
-
elif "password authentication failed" in error_str:
|
|
119
|
-
return False, (
|
|
120
|
-
f"Database authentication failed: {error_str}\n"
|
|
121
|
-
f"Please check your database credentials."
|
|
122
|
-
)
|
|
123
|
-
else:
|
|
124
|
-
return False, f"Database connection error ({error_type}): {error_str}"
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def init_database(database_url: str | None = None, force_reinit: bool = False):
|
|
128
|
-
"""Initialize shared database connection pools.
|
|
129
|
-
|
|
130
|
-
Thread-safe initialization using a lock to prevent race conditions
|
|
131
|
-
when multiple threads attempt to initialize simultaneously.
|
|
132
|
-
"""
|
|
133
|
-
global _engine, _session_local, _sync_initialized
|
|
134
|
-
|
|
135
|
-
# Get the database URL
|
|
136
|
-
db_url = database_url or get_database_url()
|
|
137
|
-
|
|
138
|
-
# Initialize Sync Engine (Global)
|
|
139
|
-
if not _sync_initialized or force_reinit:
|
|
140
|
-
with _sync_init_lock:
|
|
141
|
-
if not _sync_initialized or force_reinit:
|
|
142
|
-
sync_db_url = db_url
|
|
143
|
-
if db_url.startswith("postgresql://"):
|
|
144
|
-
sync_db_url = db_url.replace("postgresql://", "postgresql+psycopg://")
|
|
145
|
-
_engine = create_engine(
|
|
146
|
-
url=sync_db_url,
|
|
147
|
-
pool_size=settings.database_pool_size,
|
|
148
|
-
max_overflow=settings.database_max_overflow,
|
|
149
|
-
echo=settings.sql_echo,
|
|
150
|
-
)
|
|
151
|
-
_session_local = sessionmaker(autocommit=False, autoflush=False, bind=_engine)
|
|
152
|
-
_sync_initialized = True
|
|
153
|
-
logger.info("Sync database initialized", database_url=db_url)
|
|
154
|
-
|
|
155
|
-
# Initialize Async Engine (Thread-Local)
|
|
156
|
-
# Async engines must be thread-local because asyncpg connections are tied to the event loop
|
|
157
|
-
# and cannot be shared across threads/loops.
|
|
158
|
-
if not _async_db_ctx.initialized or force_reinit:
|
|
159
|
-
with _async_db_ctx.lock:
|
|
160
|
-
# Double-check after acquiring lock (another coroutine may have initialized)
|
|
161
|
-
if not _async_db_ctx.initialized or force_reinit:
|
|
162
|
-
if db_url.startswith("postgresql://"):
|
|
163
|
-
async_db_url = db_url.replace("postgresql://", "postgresql+asyncpg://")
|
|
164
|
-
_async_db_ctx.engine = create_async_engine(
|
|
165
|
-
url=async_db_url,
|
|
166
|
-
pool_size=settings.database_pool_size,
|
|
167
|
-
max_overflow=settings.database_max_overflow,
|
|
168
|
-
echo=settings.sql_echo,
|
|
169
|
-
)
|
|
170
|
-
_async_db_ctx.session_local = async_sessionmaker(
|
|
171
|
-
_async_db_ctx.engine,
|
|
172
|
-
class_=AsyncSession,
|
|
173
|
-
autocommit=False,
|
|
174
|
-
autoflush=False,
|
|
175
|
-
)
|
|
176
|
-
_async_db_ctx.initialized = True
|
|
177
|
-
logger.info(
|
|
178
|
-
"Async database initialized for thread",
|
|
179
|
-
thread_id=threading.get_ident(),
|
|
180
|
-
)
|
|
181
|
-
else:
|
|
182
|
-
logger.warning(
|
|
183
|
-
"Non-PostgreSQL URL detected, async engine not initialized",
|
|
184
|
-
url_prefix=db_url.split("://")[0] if "://" in db_url else "unknown",
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def get_engine():
|
|
189
|
-
"""Get the shared SQLAlchemy engine."""
|
|
190
|
-
if _engine is None:
|
|
191
|
-
init_database()
|
|
192
|
-
return _engine
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def get_async_engine():
|
|
196
|
-
"""Get the shared async SQLAlchemy engine."""
|
|
197
|
-
if _async_db_ctx.engine is None:
|
|
198
|
-
init_database()
|
|
199
|
-
return _async_db_ctx.engine
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
@contextmanager
|
|
203
|
-
def get_session() -> Generator[Session, None, None]:
|
|
204
|
-
"""Get a database session (sync) from shared pool."""
|
|
205
|
-
if _session_local is None:
|
|
206
|
-
init_database()
|
|
207
|
-
|
|
208
|
-
if _session_local is None:
|
|
209
|
-
raise RuntimeError("Database not initialized")
|
|
210
|
-
|
|
211
|
-
session = _session_local()
|
|
212
|
-
try:
|
|
213
|
-
yield session
|
|
214
|
-
session.commit()
|
|
215
|
-
except Exception:
|
|
216
|
-
session.rollback()
|
|
217
|
-
raise
|
|
218
|
-
finally:
|
|
219
|
-
session.close()
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
@asynccontextmanager
|
|
223
|
-
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
|
224
|
-
"""Get a database session (async) from shared pool."""
|
|
225
|
-
if _async_db_ctx.session_local is None:
|
|
226
|
-
init_database()
|
|
227
|
-
|
|
228
|
-
if _async_db_ctx.session_local is None:
|
|
229
|
-
raise RuntimeError("Async database not available (PostgreSQL required)")
|
|
230
|
-
|
|
231
|
-
async with _async_db_ctx.session_local() as session:
|
|
232
|
-
try:
|
|
233
|
-
yield session
|
|
234
|
-
await session.commit()
|
|
235
|
-
except Exception:
|
|
236
|
-
await session.rollback()
|
|
237
|
-
raise
|
|
238
|
-
finally:
|
|
239
|
-
await session.close()
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
# Dependency for FastAPI
|
|
243
|
-
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
|
244
|
-
"""FastAPI dependency for database sessions."""
|
|
245
|
-
async with get_async_session() as session:
|
|
246
|
-
yield session
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
# Test-specific database session context
|
|
250
|
-
@asynccontextmanager
|
|
251
|
-
async def get_test_db_session(database_url: str) -> AsyncGenerator[AsyncSession, None]:
|
|
252
|
-
"""Get a database session for testing with explicit database URL."""
|
|
253
|
-
from sqlalchemy.ext.asyncio import (
|
|
254
|
-
AsyncSession,
|
|
255
|
-
async_sessionmaker,
|
|
256
|
-
create_async_engine,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
from ..config import settings
|
|
260
|
-
|
|
261
|
-
# Convert to async URL for PostgreSQL
|
|
262
|
-
if database_url.startswith("postgresql://"):
|
|
263
|
-
async_url = database_url.replace("postgresql://", "postgresql+asyncpg://")
|
|
264
|
-
else:
|
|
265
|
-
async_url = database_url
|
|
266
|
-
|
|
267
|
-
# Create isolated engine for this session
|
|
268
|
-
engine = create_async_engine(
|
|
269
|
-
async_url,
|
|
270
|
-
pool_size=settings.database_pool_size,
|
|
271
|
-
max_overflow=settings.database_max_overflow,
|
|
272
|
-
echo=settings.debug,
|
|
273
|
-
isolation_level="AUTOCOMMIT", # Ensure we can see committed schema changes
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
session_local = async_sessionmaker(
|
|
277
|
-
engine,
|
|
278
|
-
class_=AsyncSession,
|
|
279
|
-
autocommit=False,
|
|
280
|
-
autoflush=False,
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
async with session_local() as session:
|
|
284
|
-
try:
|
|
285
|
-
yield session
|
|
286
|
-
await session.commit()
|
|
287
|
-
except Exception:
|
|
288
|
-
await session.rollback()
|
|
289
|
-
raise
|
|
290
|
-
finally:
|
|
291
|
-
await session.close()
|
|
292
|
-
await engine.dispose()
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# type: ignore[reportMissingImports]
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
Compatibility shim: re-export models from boards.dbmodels
|
|
5
|
-
This file remains to avoid breaking existing imports like `from boards.database.models import ...`.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from boards.dbmodels import ( # noqa: F401
|
|
9
|
-
Base,
|
|
10
|
-
BoardMembers,
|
|
11
|
-
Boards,
|
|
12
|
-
CreditTransactions,
|
|
13
|
-
Generations,
|
|
14
|
-
LoraModels,
|
|
15
|
-
ProviderConfigs,
|
|
16
|
-
Tenants,
|
|
17
|
-
Users,
|
|
18
|
-
target_metadata,
|
|
19
|
-
)
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Reusable seed data functions for database initialization.
|
|
3
|
-
|
|
4
|
-
This module provides functions to seed initial data into the database,
|
|
5
|
-
including tenants, users, and other setup-time functionality.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
from typing import Any
|
|
12
|
-
from uuid import UUID
|
|
13
|
-
|
|
14
|
-
from sqlalchemy import select
|
|
15
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
16
|
-
|
|
17
|
-
from ..config import settings
|
|
18
|
-
from ..dbmodels import Tenants
|
|
19
|
-
from ..logging import get_logger
|
|
20
|
-
|
|
21
|
-
logger = get_logger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def ensure_tenant(
|
|
25
|
-
db: AsyncSession,
|
|
26
|
-
*,
|
|
27
|
-
tenant_id: UUID | None = None,
|
|
28
|
-
name: str | None = None,
|
|
29
|
-
slug: str | None = None,
|
|
30
|
-
settings_dict: dict[str, Any] | None = None,
|
|
31
|
-
) -> UUID:
|
|
32
|
-
"""
|
|
33
|
-
Ensure a tenant exists in the database.
|
|
34
|
-
|
|
35
|
-
This function creates a tenant if it doesn't exist, or returns the
|
|
36
|
-
existing tenant's ID if it does.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
db: Database session
|
|
40
|
-
tenant_id: UUID for the tenant (if None, auto-generated)
|
|
41
|
-
name: Tenant name (defaults to env var or "Default Tenant")
|
|
42
|
-
slug: Tenant slug (defaults to env var or "default")
|
|
43
|
-
settings_dict: Optional tenant settings/metadata
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
UUID of the tenant (existing or newly created)
|
|
47
|
-
"""
|
|
48
|
-
# Use environment variables with fallbacks
|
|
49
|
-
if name is None:
|
|
50
|
-
name = os.getenv("BOARDS_TENANT_NAME", "Default Tenant")
|
|
51
|
-
|
|
52
|
-
if slug is None:
|
|
53
|
-
slug = os.getenv("BOARDS_TENANT_SLUG", settings.default_tenant_slug)
|
|
54
|
-
|
|
55
|
-
if settings_dict is None:
|
|
56
|
-
settings_dict = {}
|
|
57
|
-
|
|
58
|
-
# Check if tenant already exists by slug
|
|
59
|
-
stmt = select(Tenants).where(Tenants.slug == slug)
|
|
60
|
-
result = await db.execute(stmt)
|
|
61
|
-
existing_tenant = result.scalar_one_or_none()
|
|
62
|
-
|
|
63
|
-
if existing_tenant:
|
|
64
|
-
logger.debug(
|
|
65
|
-
"Tenant already exists",
|
|
66
|
-
tenant_id=str(existing_tenant.id),
|
|
67
|
-
slug=slug,
|
|
68
|
-
name=existing_tenant.name,
|
|
69
|
-
)
|
|
70
|
-
return existing_tenant.id
|
|
71
|
-
|
|
72
|
-
# Create new tenant
|
|
73
|
-
new_tenant = Tenants(
|
|
74
|
-
name=name,
|
|
75
|
-
slug=slug,
|
|
76
|
-
settings=settings_dict,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
# Set specific ID if provided
|
|
80
|
-
if tenant_id:
|
|
81
|
-
new_tenant.id = tenant_id
|
|
82
|
-
|
|
83
|
-
db.add(new_tenant)
|
|
84
|
-
await db.commit()
|
|
85
|
-
await db.refresh(new_tenant)
|
|
86
|
-
|
|
87
|
-
logger.info(
|
|
88
|
-
"Created new tenant",
|
|
89
|
-
tenant_id=str(new_tenant.id),
|
|
90
|
-
slug=slug,
|
|
91
|
-
name=name,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
return new_tenant.id
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
async def ensure_default_tenant(db: AsyncSession) -> UUID:
|
|
98
|
-
"""
|
|
99
|
-
Ensure the default tenant exists for single-tenant or no-auth mode.
|
|
100
|
-
|
|
101
|
-
This is a convenience function that uses environment variables
|
|
102
|
-
or defaults to create/get the default tenant.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
db: Database session
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
UUID of the default tenant
|
|
109
|
-
"""
|
|
110
|
-
return await ensure_tenant(db)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
async def seed_initial_data(db: AsyncSession) -> None:
|
|
114
|
-
"""
|
|
115
|
-
Seed all initial data required for the application.
|
|
116
|
-
|
|
117
|
-
This function can be extended to seed additional data like:
|
|
118
|
-
- Default provider configurations
|
|
119
|
-
- Initial admin users
|
|
120
|
-
- Sample boards or generations
|
|
121
|
-
- Default credit allocations
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
db: Database session
|
|
125
|
-
"""
|
|
126
|
-
logger.info("Starting database seeding")
|
|
127
|
-
|
|
128
|
-
# Always ensure default tenant exists in non-multi-tenant mode
|
|
129
|
-
if not settings.multi_tenant_mode:
|
|
130
|
-
tenant_id = await ensure_default_tenant(db)
|
|
131
|
-
logger.info("Ensured default tenant exists", tenant_id=str(tenant_id))
|
|
132
|
-
|
|
133
|
-
# Add more seed operations here as needed
|
|
134
|
-
# For example:
|
|
135
|
-
# await ensure_default_providers(db, tenant_id)
|
|
136
|
-
# await ensure_admin_user(db, tenant_id)
|
|
137
|
-
|
|
138
|
-
logger.info("Database seeding completed")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
async def seed_tenant_with_data(
|
|
142
|
-
db: AsyncSession,
|
|
143
|
-
*,
|
|
144
|
-
tenant_name: str,
|
|
145
|
-
tenant_slug: str,
|
|
146
|
-
tenant_settings: dict[str, Any] | None = None,
|
|
147
|
-
include_sample_data: bool = False,
|
|
148
|
-
) -> UUID:
|
|
149
|
-
"""
|
|
150
|
-
Create a new tenant with optional sample data.
|
|
151
|
-
|
|
152
|
-
This function is useful for:
|
|
153
|
-
- Multi-tenant setup
|
|
154
|
-
- Creating demo tenants
|
|
155
|
-
- Testing different tenant configurations
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
db: Database session
|
|
159
|
-
tenant_name: Display name for the tenant
|
|
160
|
-
tenant_slug: Unique slug for the tenant
|
|
161
|
-
tenant_settings: Optional tenant-specific settings
|
|
162
|
-
include_sample_data: Whether to create sample boards/generations
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
UUID of the created tenant
|
|
166
|
-
"""
|
|
167
|
-
tenant_id = await ensure_tenant(
|
|
168
|
-
db,
|
|
169
|
-
name=tenant_name,
|
|
170
|
-
slug=tenant_slug,
|
|
171
|
-
settings_dict=tenant_settings,
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
if include_sample_data:
|
|
175
|
-
# Future: Add sample boards, users, generations
|
|
176
|
-
logger.info(
|
|
177
|
-
"Would create sample data for tenant",
|
|
178
|
-
tenant_id=str(tenant_id),
|
|
179
|
-
note="Sample data creation not yet implemented",
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
return tenant_id
|