@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.
Files changed (237) hide show
  1. package/dist/index.js +561 -469
  2. package/dist/index.js.map +1 -1
  3. package/package.json +2 -5
  4. package/templates/README.md +0 -122
  5. package/templates/api/.env.example +0 -65
  6. package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +0 -148
  7. package/templates/api/Dockerfile +0 -32
  8. package/templates/api/README.md +0 -264
  9. package/templates/api/alembic/env.py +0 -114
  10. package/templates/api/alembic/script.py.mako +0 -28
  11. package/templates/api/alembic/versions/20250101_000000_initial_schema.py +0 -506
  12. package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +0 -75
  13. package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +0 -467
  14. package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +0 -134
  15. package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +0 -88
  16. package/templates/api/alembic.ini +0 -36
  17. package/templates/api/config/generators.yaml +0 -237
  18. package/templates/api/config/storage_config.yaml +0 -26
  19. package/templates/api/docs/ADDING_GENERATORS.md +0 -409
  20. package/templates/api/docs/GENERATORS_API.md +0 -502
  21. package/templates/api/docs/MIGRATIONS.md +0 -472
  22. package/templates/api/docs/TESTING_LIVE_APIS.md +0 -417
  23. package/templates/api/docs/storage_providers.md +0 -337
  24. package/templates/api/pyproject.toml +0 -205
  25. package/templates/api/src/boards/__init__.py +0 -10
  26. package/templates/api/src/boards/api/app.py +0 -172
  27. package/templates/api/src/boards/api/auth.py +0 -75
  28. package/templates/api/src/boards/api/endpoints/__init__.py +0 -3
  29. package/templates/api/src/boards/api/endpoints/jobs.py +0 -76
  30. package/templates/api/src/boards/api/endpoints/setup.py +0 -505
  31. package/templates/api/src/boards/api/endpoints/sse.py +0 -129
  32. package/templates/api/src/boards/api/endpoints/storage.py +0 -155
  33. package/templates/api/src/boards/api/endpoints/tenant_registration.py +0 -296
  34. package/templates/api/src/boards/api/endpoints/uploads.py +0 -149
  35. package/templates/api/src/boards/api/endpoints/webhooks.py +0 -13
  36. package/templates/api/src/boards/auth/__init__.py +0 -15
  37. package/templates/api/src/boards/auth/adapters/__init__.py +0 -27
  38. package/templates/api/src/boards/auth/adapters/auth0.py +0 -220
  39. package/templates/api/src/boards/auth/adapters/base.py +0 -73
  40. package/templates/api/src/boards/auth/adapters/clerk.py +0 -172
  41. package/templates/api/src/boards/auth/adapters/jwt.py +0 -122
  42. package/templates/api/src/boards/auth/adapters/none.py +0 -102
  43. package/templates/api/src/boards/auth/adapters/oidc.py +0 -284
  44. package/templates/api/src/boards/auth/adapters/supabase.py +0 -110
  45. package/templates/api/src/boards/auth/context.py +0 -35
  46. package/templates/api/src/boards/auth/factory.py +0 -129
  47. package/templates/api/src/boards/auth/middleware.py +0 -221
  48. package/templates/api/src/boards/auth/provisioning.py +0 -129
  49. package/templates/api/src/boards/auth/tenant_extraction.py +0 -278
  50. package/templates/api/src/boards/cli.py +0 -354
  51. package/templates/api/src/boards/config.py +0 -131
  52. package/templates/api/src/boards/database/__init__.py +0 -7
  53. package/templates/api/src/boards/database/cli.py +0 -110
  54. package/templates/api/src/boards/database/connection.py +0 -292
  55. package/templates/api/src/boards/database/models.py +0 -19
  56. package/templates/api/src/boards/database/seed_data.py +0 -182
  57. package/templates/api/src/boards/dbmodels/__init__.py +0 -441
  58. package/templates/api/src/boards/generators/__init__.py +0 -57
  59. package/templates/api/src/boards/generators/artifact_resolution.py +0 -405
  60. package/templates/api/src/boards/generators/artifacts.py +0 -53
  61. package/templates/api/src/boards/generators/base.py +0 -144
  62. package/templates/api/src/boards/generators/implementations/__init__.py +0 -14
  63. package/templates/api/src/boards/generators/implementations/fal/__init__.py +0 -25
  64. package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +0 -23
  65. package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +0 -171
  66. package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +0 -167
  67. package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_text_to_speech.py +0 -176
  68. package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_tts_turbo.py +0 -195
  69. package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +0 -194
  70. package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +0 -209
  71. package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +0 -206
  72. package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +0 -237
  73. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +0 -173
  74. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +0 -221
  75. package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +0 -63
  76. package/templates/api/src/boards/generators/implementations/fal/image/bytedance_seedream_v45_edit.py +0 -219
  77. package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +0 -220
  78. package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +0 -173
  79. package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +0 -227
  80. package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +0 -203
  81. package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +0 -230
  82. package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +0 -204
  83. package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +0 -221
  84. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +0 -216
  85. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +0 -197
  86. package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +0 -177
  87. package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image_edit.py +0 -208
  88. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_15_edit.py +0 -216
  89. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_5.py +0 -177
  90. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +0 -182
  91. package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +0 -167
  92. package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +0 -299
  93. package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +0 -190
  94. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +0 -191
  95. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +0 -179
  96. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +0 -183
  97. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +0 -212
  98. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +0 -179
  99. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +0 -226
  100. package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +0 -249
  101. package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +0 -244
  102. package/templates/api/src/boards/generators/implementations/fal/image/reve_edit.py +0 -178
  103. package/templates/api/src/boards/generators/implementations/fal/image/reve_text_to_image.py +0 -155
  104. package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py +0 -180
  105. package/templates/api/src/boards/generators/implementations/fal/utils.py +0 -61
  106. package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +0 -77
  107. package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +0 -209
  108. package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +0 -161
  109. package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +0 -222
  110. package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +0 -152
  111. package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +0 -197
  112. package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +0 -173
  113. package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +0 -221
  114. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_pro.py +0 -168
  115. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_standard.py +0 -159
  116. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +0 -175
  117. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +0 -168
  118. package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +0 -153
  119. package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +0 -172
  120. package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +0 -175
  121. package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +0 -163
  122. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +0 -167
  123. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +0 -155
  124. package/templates/api/src/boards/generators/implementations/fal/video/veed_fabric_1_0.py +0 -180
  125. package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +0 -174
  126. package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +0 -194
  127. package/templates/api/src/boards/generators/implementations/fal/video/veo31.py +0 -190
  128. package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast.py +0 -190
  129. package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast_image_to_video.py +0 -191
  130. package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +0 -187
  131. package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +0 -183
  132. package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +0 -172
  133. package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_image_to_video.py +0 -212
  134. package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_text_to_video.py +0 -208
  135. package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +0 -158
  136. package/templates/api/src/boards/generators/implementations/kie/__init__.py +0 -11
  137. package/templates/api/src/boards/generators/implementations/kie/base.py +0 -316
  138. package/templates/api/src/boards/generators/implementations/kie/image/__init__.py +0 -3
  139. package/templates/api/src/boards/generators/implementations/kie/image/nano_banana_edit.py +0 -190
  140. package/templates/api/src/boards/generators/implementations/kie/utils.py +0 -98
  141. package/templates/api/src/boards/generators/implementations/kie/video/__init__.py +0 -8
  142. package/templates/api/src/boards/generators/implementations/kie/video/veo3.py +0 -161
  143. package/templates/api/src/boards/generators/implementations/openai/__init__.py +0 -1
  144. package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +0 -1
  145. package/templates/api/src/boards/generators/implementations/openai/audio/whisper.py +0 -69
  146. package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +0 -1
  147. package/templates/api/src/boards/generators/implementations/openai/image/dalle3.py +0 -96
  148. package/templates/api/src/boards/generators/implementations/replicate/__init__.py +0 -1
  149. package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +0 -1
  150. package/templates/api/src/boards/generators/implementations/replicate/image/flux_pro.py +0 -88
  151. package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +0 -1
  152. package/templates/api/src/boards/generators/implementations/replicate/video/lipsync.py +0 -73
  153. package/templates/api/src/boards/generators/loader.py +0 -253
  154. package/templates/api/src/boards/generators/registry.py +0 -114
  155. package/templates/api/src/boards/generators/resolution.py +0 -632
  156. package/templates/api/src/boards/generators/testmods/class_gen.py +0 -34
  157. package/templates/api/src/boards/generators/testmods/import_side_effect.py +0 -35
  158. package/templates/api/src/boards/graphql/__init__.py +0 -7
  159. package/templates/api/src/boards/graphql/access_control.py +0 -136
  160. package/templates/api/src/boards/graphql/mutations/root.py +0 -148
  161. package/templates/api/src/boards/graphql/queries/root.py +0 -116
  162. package/templates/api/src/boards/graphql/resolvers/__init__.py +0 -8
  163. package/templates/api/src/boards/graphql/resolvers/auth.py +0 -12
  164. package/templates/api/src/boards/graphql/resolvers/board.py +0 -1053
  165. package/templates/api/src/boards/graphql/resolvers/generation.py +0 -666
  166. package/templates/api/src/boards/graphql/resolvers/generator.py +0 -50
  167. package/templates/api/src/boards/graphql/resolvers/lineage.py +0 -381
  168. package/templates/api/src/boards/graphql/resolvers/upload.py +0 -463
  169. package/templates/api/src/boards/graphql/resolvers/user.py +0 -25
  170. package/templates/api/src/boards/graphql/schema.py +0 -81
  171. package/templates/api/src/boards/graphql/types/board.py +0 -102
  172. package/templates/api/src/boards/graphql/types/generation.py +0 -166
  173. package/templates/api/src/boards/graphql/types/generator.py +0 -17
  174. package/templates/api/src/boards/graphql/types/user.py +0 -47
  175. package/templates/api/src/boards/jobs/repository.py +0 -153
  176. package/templates/api/src/boards/logging.py +0 -195
  177. package/templates/api/src/boards/middleware.py +0 -339
  178. package/templates/api/src/boards/progress/__init__.py +0 -4
  179. package/templates/api/src/boards/progress/models.py +0 -25
  180. package/templates/api/src/boards/progress/publisher.py +0 -64
  181. package/templates/api/src/boards/py.typed +0 -0
  182. package/templates/api/src/boards/redis_pool.py +0 -118
  183. package/templates/api/src/boards/storage/__init__.py +0 -52
  184. package/templates/api/src/boards/storage/base.py +0 -363
  185. package/templates/api/src/boards/storage/config.py +0 -187
  186. package/templates/api/src/boards/storage/factory.py +0 -288
  187. package/templates/api/src/boards/storage/implementations/__init__.py +0 -27
  188. package/templates/api/src/boards/storage/implementations/gcs.py +0 -340
  189. package/templates/api/src/boards/storage/implementations/local.py +0 -201
  190. package/templates/api/src/boards/storage/implementations/s3.py +0 -294
  191. package/templates/api/src/boards/storage/implementations/supabase.py +0 -218
  192. package/templates/api/src/boards/tenant_isolation.py +0 -446
  193. package/templates/api/src/boards/validation.py +0 -262
  194. package/templates/api/src/boards/workers/__init__.py +0 -1
  195. package/templates/api/src/boards/workers/actors.py +0 -274
  196. package/templates/api/src/boards/workers/cli.py +0 -125
  197. package/templates/api/src/boards/workers/context.py +0 -348
  198. package/templates/api/src/boards/workers/middleware.py +0 -58
  199. package/templates/api/src/py.typed +0 -0
  200. package/templates/compose.web.yaml +0 -35
  201. package/templates/compose.yaml +0 -116
  202. package/templates/docker/env.example +0 -23
  203. package/templates/web/.env.example +0 -28
  204. package/templates/web/Dockerfile +0 -51
  205. package/templates/web/components.json +0 -22
  206. package/templates/web/imageLoader.js +0 -18
  207. package/templates/web/next-env.d.ts +0 -5
  208. package/templates/web/next.config.js +0 -36
  209. package/templates/web/package.json +0 -41
  210. package/templates/web/postcss.config.mjs +0 -7
  211. package/templates/web/public/favicon.ico +0 -0
  212. package/templates/web/src/app/boards/[boardId]/page.tsx +0 -353
  213. package/templates/web/src/app/globals.css +0 -123
  214. package/templates/web/src/app/layout.tsx +0 -31
  215. package/templates/web/src/app/lineage/[generationId]/page.tsx +0 -235
  216. package/templates/web/src/app/page.tsx +0 -35
  217. package/templates/web/src/app/providers.tsx +0 -18
  218. package/templates/web/src/components/boards/ArtifactInputSlots.tsx +0 -206
  219. package/templates/web/src/components/boards/ArtifactPreview.tsx +0 -466
  220. package/templates/web/src/components/boards/GenerationGrid.tsx +0 -282
  221. package/templates/web/src/components/boards/GenerationInput.tsx +0 -370
  222. package/templates/web/src/components/boards/GeneratorSelector.tsx +0 -272
  223. package/templates/web/src/components/boards/UploadArtifact.tsx +0 -563
  224. package/templates/web/src/components/header.tsx +0 -32
  225. package/templates/web/src/components/theme-provider.tsx +0 -10
  226. package/templates/web/src/components/theme-toggle.tsx +0 -75
  227. package/templates/web/src/components/ui/alert-dialog.tsx +0 -157
  228. package/templates/web/src/components/ui/button.tsx +0 -58
  229. package/templates/web/src/components/ui/card.tsx +0 -92
  230. package/templates/web/src/components/ui/dropdown-menu.tsx +0 -200
  231. package/templates/web/src/components/ui/navigation-menu.tsx +0 -168
  232. package/templates/web/src/components/ui/toast.tsx +0 -128
  233. package/templates/web/src/components/ui/toaster.tsx +0 -35
  234. package/templates/web/src/components/ui/use-toast.ts +0 -187
  235. package/templates/web/src/hooks/useGeneratorMRU.ts +0 -57
  236. package/templates/web/src/lib/utils.ts +0 -6
  237. package/templates/web/tsconfig.json +0 -41
@@ -1,262 +0,0 @@
1
- """
2
- Configuration validation for Boards application.
3
-
4
- This module provides validation functions to ensure the application
5
- is properly configured before startup.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from typing import Any
11
-
12
- from .config import settings
13
- from .database.connection import get_async_session, test_database_connection
14
- from .database.seed_data import ensure_default_tenant
15
- from .logging import get_logger
16
-
17
- logger = get_logger(__name__)
18
-
19
-
20
- class ValidationError(Exception):
21
- """Raised when application validation fails."""
22
-
23
- pass
24
-
25
-
26
- async def validate_database_connection() -> dict[str, Any]:
27
- """
28
- Validate that the database is accessible and responsive.
29
-
30
- This runs before other validation checks to catch connection issues early
31
- with clear error messages.
32
-
33
- Returns a dictionary with validation results and connection details.
34
- """
35
- results = {
36
- "valid": True,
37
- "warnings": [],
38
- "errors": [],
39
- "connection_info": None,
40
- }
41
-
42
- success, error_message = await test_database_connection()
43
-
44
- if success:
45
- results["connection_info"] = {
46
- "status": "connected",
47
- "message": "Database connection successful",
48
- }
49
- logger.info("Database connection validation successful")
50
- else:
51
- results["valid"] = False
52
- results["errors"].append(error_message)
53
- logger.error("Database connection validation failed", error=error_message)
54
-
55
- return results
56
-
57
-
58
- async def validate_tenant_configuration() -> dict[str, Any]:
59
- """
60
- Validate tenant configuration and setup.
61
-
62
- Returns a dictionary with validation results and recommendations.
63
- Raises ValidationError if critical validation fails.
64
- """
65
- results = {
66
- "valid": True,
67
- "warnings": [],
68
- "errors": [],
69
- "tenant_info": None,
70
- }
71
-
72
- try:
73
- async with get_async_session() as db:
74
- if settings.multi_tenant_mode:
75
- # Multi-tenant mode - just validate database connection
76
- results["tenant_info"] = {
77
- "mode": "multi_tenant",
78
- "message": "Multi-tenant mode enabled - tenants managed dynamically",
79
- }
80
- logger.info("Tenant validation: Multi-tenant mode configured")
81
-
82
- else:
83
- # Single-tenant mode - ensure default tenant exists
84
- try:
85
- tenant_id = await ensure_default_tenant(db)
86
- results["tenant_info"] = {
87
- "mode": "single_tenant",
88
- "tenant_id": str(tenant_id),
89
- "slug": settings.default_tenant_slug,
90
- "message": f"Default tenant exists: {tenant_id}",
91
- }
92
- logger.info(
93
- "Tenant validation: Default tenant verified",
94
- tenant_id=str(tenant_id),
95
- slug=settings.default_tenant_slug,
96
- )
97
-
98
- except Exception as e:
99
- error_msg = f"Failed to ensure default tenant exists: {str(e)}"
100
- results["errors"].append(error_msg)
101
- results["valid"] = False
102
- logger.error("Tenant validation failed", error=str(e))
103
-
104
- except Exception as e:
105
- error_msg = f"Database connection failed during tenant validation: {str(e)}"
106
- results["errors"].append(error_msg)
107
- results["valid"] = False
108
- logger.error("Database validation failed", error=str(e))
109
-
110
- return results
111
-
112
-
113
- async def validate_auth_configuration() -> dict[str, Any]:
114
- """
115
- Validate authentication configuration.
116
-
117
- Returns validation results and security recommendations.
118
- """
119
- results = {
120
- "valid": True,
121
- "warnings": [],
122
- "errors": [],
123
- "auth_info": {
124
- "provider": settings.auth_provider,
125
- "multi_tenant_mode": settings.multi_tenant_mode,
126
- },
127
- }
128
-
129
- # Validate auth provider configuration
130
- if settings.auth_provider == "none":
131
- if settings.environment.lower() in ("production", "prod"):
132
- warning = "No-auth mode detected in production environment - this is a security risk!"
133
- results["warnings"].append(warning)
134
- logger.warning(warning)
135
- else:
136
- logger.info("Auth validation: No-auth mode enabled for development")
137
-
138
- elif settings.auth_provider == "jwt":
139
- if not settings.jwt_secret:
140
- error = "JWT authentication enabled but JWT_SECRET not configured"
141
- results["errors"].append(error)
142
- results["valid"] = False
143
- logger.error(error)
144
- else:
145
- logger.info("Auth validation: JWT authentication configured")
146
-
147
- else:
148
- logger.info(f"Auth validation: Using {settings.auth_provider} provider")
149
-
150
- return results
151
-
152
-
153
- async def validate_startup_configuration() -> dict[str, Any]:
154
- """
155
- Comprehensive startup validation.
156
-
157
- This function should be called during application startup to ensure
158
- all critical configuration is valid.
159
- """
160
- logger.info("Starting application configuration validation")
161
-
162
- # Test database connection first - this catches common issues early
163
- db_results = await validate_database_connection()
164
-
165
- # Only proceed with other validations if database is accessible
166
- if db_results["valid"]:
167
- tenant_results = await validate_tenant_configuration()
168
- auth_results = await validate_auth_configuration()
169
- else:
170
- # Skip tenant/auth validation if database is not accessible
171
- logger.warning("Skipping tenant and auth validation due to database connection failure")
172
- tenant_results = {
173
- "valid": False,
174
- "warnings": [],
175
- "errors": ["Skipped due to database connection failure"],
176
- "tenant_info": None,
177
- }
178
- auth_results = await validate_auth_configuration() # Auth can validate without DB
179
-
180
- # Combine results
181
- combined_results = {
182
- "overall_valid": db_results["valid"] and tenant_results["valid"] and auth_results["valid"],
183
- "database": db_results,
184
- "tenant": tenant_results,
185
- "auth": auth_results,
186
- "environment": {
187
- "auth_provider": settings.auth_provider,
188
- "multi_tenant_mode": settings.multi_tenant_mode,
189
- "environment": settings.environment,
190
- "debug": settings.debug,
191
- },
192
- }
193
-
194
- # Log summary
195
- if combined_results["overall_valid"]:
196
- logger.info("Application configuration validation completed successfully")
197
- else:
198
- all_errors = (
199
- db_results.get("errors", [])
200
- + tenant_results.get("errors", [])
201
- + auth_results.get("errors", [])
202
- )
203
- logger.error(
204
- "Application configuration validation failed",
205
- errors=all_errors,
206
- )
207
-
208
- # Log warnings
209
- all_warnings = (
210
- db_results.get("warnings", [])
211
- + tenant_results.get("warnings", [])
212
- + auth_results.get("warnings", [])
213
- )
214
- if all_warnings:
215
- logger.warning(
216
- "Configuration warnings detected",
217
- warnings=all_warnings,
218
- )
219
-
220
- return combined_results
221
-
222
-
223
- def get_startup_recommendations(validation_results: dict[str, Any]) -> list[str]:
224
- """
225
- Generate startup recommendations based on validation results.
226
- """
227
- recommendations = []
228
-
229
- # Database recommendations
230
- if not validation_results.get("database", {}).get("valid", False):
231
- recommendations.append(
232
- "Database connection failed - check that PostgreSQL is running and accessible"
233
- )
234
- return recommendations # Return early if database is not accessible
235
-
236
- # Tenant recommendations
237
- tenant_info = validation_results["tenant"].get("tenant_info")
238
- if tenant_info and tenant_info["mode"] == "single_tenant":
239
- recommendations.append(f"Single-tenant mode active with tenant: {tenant_info['tenant_id']}")
240
-
241
- # Auth recommendations
242
- auth_info = validation_results["auth"]["auth_info"]
243
- if auth_info["provider"] == "none" and settings.environment.lower() not in (
244
- "development",
245
- "dev",
246
- ):
247
- recommendations.append(
248
- "Consider configuring a proper authentication provider for non-development environments"
249
- )
250
-
251
- # Error recommendations
252
- if not validation_results["overall_valid"]:
253
- recommendations.append("Fix configuration errors before deploying to production")
254
-
255
- # Success recommendations
256
- if validation_results["overall_valid"] and not recommendations:
257
- if auth_info["multi_tenant_mode"]:
258
- recommendations.append("Multi-tenant configuration is ready for operation")
259
- else:
260
- recommendations.append("Single-tenant configuration is ready for operation")
261
-
262
- return recommendations
@@ -1 +0,0 @@
1
- """Workers package: Dramatiq actors and execution utilities."""
@@ -1,274 +0,0 @@
1
- """Dramatiq actors for generation processing."""
2
-
3
- from __future__ import annotations
4
-
5
- import traceback
6
- from typing import Any
7
-
8
- import dramatiq
9
- from dramatiq import actor
10
- from dramatiq.brokers.redis import RedisBroker
11
- from dramatiq.middleware import AsyncIO
12
-
13
- from ..config import Settings
14
- from ..database.connection import get_async_session
15
- from ..generators.registry import registry as generator_registry
16
- from ..jobs import repository as jobs_repo
17
- from ..logging import get_logger
18
- from ..progress.models import ProgressUpdate
19
- from ..progress.publisher import ProgressPublisher
20
- from ..storage.factory import create_storage_manager
21
- from .context import GeneratorExecutionContext
22
- from .middleware import GeneratorLoaderMiddleware
23
-
24
- logger = get_logger(__name__)
25
-
26
-
27
- settings = Settings()
28
- broker = RedisBroker(url=settings.redis_url)
29
- dramatiq.set_broker(broker)
30
-
31
- # Enable async actor support - Dramatiq will manage event loops per thread
32
- # This avoids the event loop conflicts from using asyncio.run()
33
- broker.add_middleware(AsyncIO())
34
-
35
- # Load generators when worker process starts via middleware
36
- # Middleware runs before_worker_boot hook once per worker process at startup
37
- broker.add_middleware(GeneratorLoaderMiddleware())
38
-
39
-
40
- @actor(queue_name="boards-jobs", max_retries=3, min_backoff=5000, max_backoff=30000)
41
- async def process_generation(generation_id: str) -> None:
42
- """Entry actor: load job context and dispatch to the generator.
43
-
44
- Retry policy:
45
- - max_retries: 3 attempts
46
- - min_backoff: 5 seconds
47
- - max_backoff: 30 seconds
48
-
49
- Note: This is an async actor. Dramatiq manages the event loop lifecycle properly,
50
- avoiding the event loop conflicts that would occur with asyncio.run().
51
-
52
- Process a generation job with comprehensive error handling.
53
- """
54
- logger.info("Starting generation processing", generation_id=generation_id)
55
-
56
- publisher = ProgressPublisher(settings)
57
-
58
- try:
59
- # Initialize processing
60
- await publisher.publish_progress(
61
- generation_id,
62
- ProgressUpdate(
63
- job_id=generation_id,
64
- status="processing",
65
- progress=0.0,
66
- phase="initializing",
67
- ),
68
- )
69
-
70
- # Load generation from DB
71
- async with get_async_session() as session:
72
- gen = await jobs_repo.get_generation(session, generation_id)
73
- # Access all attributes while session is active to avoid DetachedInstanceError
74
- generator_name = gen.generator_name
75
- input_params = gen.input_params
76
- gen_id = gen.id
77
- tenant_id = gen.tenant_id
78
- board_id = gen.board_id
79
- user_id = gen.user_id
80
- artifact_type = gen.artifact_type
81
-
82
- # Initialize storage manager
83
- # This will use the default storage configuration from environment/config
84
- storage_manager = create_storage_manager()
85
-
86
- # Validate generator exists
87
- generator = generator_registry.get(generator_name)
88
- if generator is None:
89
- error_msg = "Unknown generator"
90
- logger.error(error_msg, generator_name=generator_name)
91
- raise RuntimeError(f"Unknown generator: {generator_name}")
92
-
93
- # Build and validate typed inputs
94
- # First resolve any artifact fields (generation IDs -> artifact objects)
95
- # This happens automatically via type introspection
96
- lineage_metadata: list[dict[str, Any]] = []
97
- try:
98
- input_schema = generator.get_input_schema()
99
-
100
- # Automatically resolve generation IDs to artifacts before validation
101
- from ..generators.artifact_resolution import resolve_input_artifacts
102
-
103
- async with get_async_session() as session:
104
- resolved_params, lineage_metadata = await resolve_input_artifacts(
105
- input_params,
106
- input_schema, # Schema is introspected to find artifact fields
107
- session,
108
- tenant_id,
109
- )
110
-
111
- # Store lineage metadata in the generation
112
- if lineage_metadata:
113
- generation = await jobs_repo.get_generation(session, generation_id)
114
- generation.input_artifacts = lineage_metadata
115
- await session.commit()
116
-
117
- typed_inputs = input_schema.model_validate(resolved_params)
118
- except Exception as e:
119
- error_msg = "Invalid input parameters"
120
- logger.error(error_msg, generation_id=generation_id, error=str(e))
121
- raise ValueError(f"Invalid input parameters: {e}") from e
122
-
123
- # Build context and run generator
124
- context = GeneratorExecutionContext(
125
- gen_id,
126
- publisher,
127
- storage_manager,
128
- tenant_id,
129
- board_id,
130
- user_id,
131
- generator_name,
132
- artifact_type,
133
- input_params,
134
- )
135
-
136
- await publisher.publish_progress(
137
- generation_id,
138
- ProgressUpdate(
139
- job_id=generation_id,
140
- status="processing",
141
- progress=0.05,
142
- phase="processing",
143
- message="Starting generation",
144
- ),
145
- )
146
-
147
- # Execute generator
148
- logger.info(
149
- "Executing generator",
150
- generator_name=generator_name,
151
- generation_id=generation_id,
152
- )
153
- # TODO: Consider implementing credit refund logic on failure
154
- # await refund_credits(gen.user_id, gen.estimated_cost)
155
- output = await generator.generate(typed_inputs, context)
156
- logger.info(
157
- "Generator completed successfully",
158
- generator_name=generator_name,
159
- generation_id=generation_id,
160
- artifact_count=len(output.outputs),
161
- )
162
-
163
- # Find the artifact with matching generation_id (primary generation)
164
- # Generators should return exactly one artifact with the matching generation_id
165
- matching_artifacts = [art for art in output.outputs if art.generation_id == generation_id]
166
-
167
- if len(matching_artifacts) == 0:
168
- raise RuntimeError(
169
- f"No artifact found with generation_id {generation_id} in generator output. "
170
- f"Generator returned {len(output.outputs)} artifact(s) but none matched."
171
- )
172
-
173
- if len(matching_artifacts) > 1:
174
- logger.warning(
175
- "Generator returned multiple artifacts with same generation_id, using first one",
176
- generation_id=generation_id,
177
- artifact_count=len(matching_artifacts),
178
- )
179
-
180
- artifact = matching_artifacts[0]
181
-
182
- # Extract storage URL and convert artifact to dict
183
- storage_url = artifact.storage_url
184
- output_metadata = artifact.model_dump()
185
-
186
- # If this was a batch generation, add batch metadata to primary generation
187
- if context._batch_id is not None:
188
- output_metadata["batch_id"] = context._batch_id
189
- output_metadata["batch_index"] = 0
190
- output_metadata["batch_size"] = len(output.outputs)
191
- logger.info(
192
- "Primary generation is part of batch",
193
- generation_id=generation_id,
194
- batch_id=context._batch_id,
195
- batch_size=len(output.outputs),
196
- )
197
-
198
- # Finalize DB with storage URL and output metadata
199
- async with get_async_session() as session:
200
- await jobs_repo.finalize_success(
201
- session,
202
- generation_id,
203
- storage_url=storage_url,
204
- output_metadata=output_metadata,
205
- )
206
-
207
- # Finalize all batch generation records (if any)
208
- if context._batch_id is not None:
209
- batch_artifacts = [art for art in output.outputs if art.generation_id != generation_id]
210
- logger.info(
211
- "Finalizing batch generation records",
212
- batch_id=context._batch_id,
213
- batch_count=len(batch_artifacts),
214
- )
215
- for batch_artifact in batch_artifacts:
216
- async with get_async_session() as session:
217
- batch_metadata = batch_artifact.model_dump()
218
- # Add batch metadata to each batch generation
219
- batch_metadata["batch_id"] = context._batch_id
220
- # batch_index was set during generation creation via create_batch_generation()
221
- batch_metadata["batch_size"] = len(output.outputs)
222
-
223
- await jobs_repo.finalize_success(
224
- session,
225
- batch_artifact.generation_id,
226
- storage_url=batch_artifact.storage_url,
227
- output_metadata=batch_metadata,
228
- )
229
- logger.info(
230
- "Batch generation finalized",
231
- batch_generation_id=batch_artifact.generation_id,
232
- batch_id=context._batch_id,
233
- )
234
-
235
- logger.info("Job finalized successfully", generation_id=generation_id)
236
-
237
- # Publish completion (DB already updated by finalize_success)
238
- await publisher.publish_only(
239
- generation_id,
240
- ProgressUpdate(
241
- job_id=generation_id,
242
- status="completed",
243
- progress=1.0,
244
- phase="finalizing",
245
- message="Completed",
246
- ),
247
- )
248
-
249
- except Exception as e:
250
- # Log the full traceback for debugging
251
- logger.error(
252
- "Job failed with error",
253
- generation_id=generation_id,
254
- error=str(e),
255
- traceback=traceback.format_exc(),
256
- )
257
-
258
- # Publish failure status (this also persists to DB via ProgressPublisher)
259
- try:
260
- await publisher.publish_progress(
261
- generation_id,
262
- ProgressUpdate(
263
- job_id=generation_id,
264
- status="failed",
265
- progress=0.0,
266
- phase="finalizing",
267
- message=str(e),
268
- ),
269
- )
270
- except Exception as pub_error:
271
- logger.error("Failed to publish error status", error=str(pub_error))
272
-
273
- # Re-raise for Dramatiq retry mechanism
274
- # raise
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- CLI entry point for Boards background workers.
4
-
5
- For auto-reload during development, use a file watcher like entr or nodemon:
6
-
7
- # Using entr (recommended):
8
- make dev-worker-watch
9
-
10
- # Or manually with entr:
11
- find packages/backend/src -name '*.py' | entr -r uv run boards-worker
12
-
13
- # Using nodemon:
14
- nodemon --watch packages/backend/src --exec "uv run boards-worker"
15
- """
16
-
17
- import sys
18
-
19
- import click
20
-
21
- from boards import __version__
22
- from boards.logging import configure_logging, get_logger
23
-
24
- logger = get_logger(__name__)
25
-
26
-
27
- def start_worker(
28
- processes: int,
29
- threads: int,
30
- queue_list: list[str],
31
- log_level: str,
32
- ) -> None:
33
- """Start the Dramatiq worker process."""
34
- # Configure logging
35
- configure_logging(debug=(log_level == "debug"))
36
-
37
- try:
38
- # Import workers to register them (if they exist)
39
- try:
40
- from boards.workers import actors # noqa: F401
41
- except ImportError:
42
- logger.warning("No worker actors found - continuing with empty worker")
43
-
44
- # Start the worker
45
- from dramatiq.cli import main as dramatiq_main
46
-
47
- # Build dramatiq CLI args
48
- args = [
49
- "dramatiq",
50
- "boards.workers.actors",
51
- f"--processes={processes}",
52
- f"--threads={threads}",
53
- ]
54
-
55
- for queue in queue_list:
56
- args.extend(["--queues", queue])
57
-
58
- # Override sys.argv for dramatiq CLI
59
- original_argv = sys.argv
60
- sys.argv = args
61
-
62
- dramatiq_main()
63
-
64
- except KeyboardInterrupt:
65
- logger.info("Worker shutdown requested by user")
66
- except Exception as e:
67
- logger.error("Worker startup failed", error=str(e))
68
- sys.exit(1)
69
- finally:
70
- # Restore original argv
71
- sys.argv = original_argv
72
-
73
-
74
- @click.command()
75
- @click.option(
76
- "--processes",
77
- default=1,
78
- type=int,
79
- help="Number of worker processes (default: 1)",
80
- )
81
- @click.option(
82
- "--threads",
83
- default=1,
84
- type=int,
85
- help="Number of worker threads per process (default: 1)",
86
- )
87
- @click.option(
88
- "--queues",
89
- default="boards-jobs",
90
- help="Comma-separated list of queues to process (default: boards-jobs)",
91
- )
92
- @click.option(
93
- "--log-level",
94
- default="info",
95
- type=click.Choice(["debug", "info", "warning", "error"]),
96
- help="Log level (default: info)",
97
- )
98
- @click.version_option(version=__version__, prog_name="boards-worker")
99
- def main(
100
- processes: int,
101
- threads: int,
102
- queues: str,
103
- log_level: str,
104
- ) -> None:
105
- """Start Boards background workers."""
106
-
107
- # Configure logging
108
- configure_logging(debug=(log_level == "debug"))
109
-
110
- queue_list = [q.strip() for q in queues.split(",")]
111
-
112
- logger.info(
113
- "Starting Boards workers",
114
- processes=processes,
115
- threads=threads,
116
- queues=queue_list,
117
- log_level=log_level,
118
- )
119
-
120
- start_worker(processes, threads, queue_list, log_level)
121
-
122
-
123
- # meaningless
124
- if __name__ == "__main__":
125
- main()