@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,666 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
- from uuid import UUID
5
-
6
- import strawberry
7
- from sqlalchemy import or_, select
8
- from sqlalchemy.orm import selectinload
9
-
10
- from ...database.connection import get_async_session
11
- from ...dbmodels import BoardMembers, Boards, Generations, Users
12
- from ...generators.registry import registry as generator_registry
13
- from ...jobs import repository as jobs_repo
14
- from ...logging import get_logger
15
- from ...workers.actors import process_generation
16
- from ..access_control import can_access_board, get_auth_context_from_info
17
-
18
- if TYPE_CHECKING:
19
- from ..mutations.root import CreateGenerationInput
20
- from ..types.board import Board
21
- from ..types.generation import ArtifactType, Generation, GenerationStatus
22
- from ..types.user import User
23
-
24
- logger = get_logger(__name__)
25
-
26
-
27
- # Query resolvers
28
- async def resolve_generation_by_id(info: strawberry.Info, id: UUID) -> Generation | None:
29
- """
30
- Resolve a generation by its ID.
31
-
32
- Checks authorization: user must have access to the generation's board.
33
- """
34
- auth_context = await get_auth_context_from_info(info)
35
- if auth_context is None:
36
- return None
37
-
38
- async with get_async_session() as session:
39
- # Query generation
40
- stmt = select(Generations).where(Generations.id == id)
41
- result = await session.execute(stmt)
42
- gen = result.scalar_one_or_none()
43
-
44
- if not gen:
45
- logger.info("Generation not found", generation_id=str(id))
46
- return None
47
-
48
- # Check board access
49
- board_stmt = (
50
- select(Boards)
51
- .where(Boards.id == gen.board_id)
52
- .options(selectinload(Boards.board_members))
53
- )
54
- board_result = await session.execute(board_stmt)
55
- board = board_result.scalar_one_or_none()
56
-
57
- if not board or not can_access_board(board, auth_context):
58
- logger.info(
59
- "Access denied to generation",
60
- generation_id=str(id),
61
- board_id=str(gen.board_id),
62
- user_id=(
63
- str(auth_context.user_id) if auth_context and auth_context.user_id else None
64
- ),
65
- )
66
- return None
67
-
68
- # Convert to GraphQL type
69
- from ..types.generation import ArtifactType, GenerationStatus
70
- from ..types.generation import Generation as GenerationType
71
-
72
- return GenerationType(
73
- id=gen.id,
74
- tenant_id=gen.tenant_id,
75
- board_id=gen.board_id,
76
- user_id=gen.user_id,
77
- generator_name=gen.generator_name,
78
- artifact_type=ArtifactType(gen.artifact_type),
79
- storage_url=gen.storage_url,
80
- thumbnail_url=gen.thumbnail_url,
81
- additional_files=gen.additional_files or [],
82
- input_params=gen.input_params or {},
83
- output_metadata=gen.output_metadata or {},
84
- external_job_id=gen.external_job_id,
85
- status=GenerationStatus(gen.status),
86
- progress=float(gen.progress or 0.0),
87
- error_message=gen.error_message,
88
- started_at=gen.started_at,
89
- completed_at=gen.completed_at,
90
- created_at=gen.created_at,
91
- updated_at=gen.updated_at,
92
- )
93
-
94
-
95
- async def resolve_recent_generations(
96
- info: strawberry.Info,
97
- board_id: UUID | None,
98
- status: GenerationStatus | None,
99
- artifact_type: ArtifactType | None,
100
- limit: int,
101
- offset: int,
102
- ) -> list[Generation]:
103
- """
104
- Resolve recent generations with filtering.
105
-
106
- If board_id is None, returns generations from all boards the user has access to.
107
- """
108
- auth_context = await get_auth_context_from_info(info)
109
- if not auth_context or not auth_context.is_authenticated:
110
- logger.info("Unauthenticated access to recent_generations")
111
- return []
112
-
113
- async with get_async_session() as session:
114
- # Build base query
115
- generations_query = select(Generations)
116
-
117
- # Apply filters
118
- if board_id is not None:
119
- # Check access to specific board
120
- board_stmt = (
121
- select(Boards)
122
- .where(Boards.id == board_id)
123
- .options(selectinload(Boards.board_members))
124
- )
125
- board_result = await session.execute(board_stmt)
126
- board = board_result.scalar_one_or_none()
127
-
128
- if not board or not can_access_board(board, auth_context):
129
- logger.info(
130
- "Access denied to board for recent generations",
131
- board_id=str(board_id),
132
- user_id=str(auth_context.user_id),
133
- )
134
- return []
135
-
136
- generations_query = generations_query.where(Generations.board_id == board_id)
137
- else:
138
- # Get all boards user has access to
139
- member_board_ids = select(BoardMembers.board_id).where(
140
- BoardMembers.user_id == auth_context.user_id
141
- )
142
- accessible_boards_condition = or_(
143
- Boards.owner_id == auth_context.user_id,
144
- Boards.id.in_(member_board_ids),
145
- Boards.is_public,
146
- )
147
- accessible_boards_stmt = select(Boards.id).where(accessible_boards_condition)
148
- accessible_boards_result = await session.execute(accessible_boards_stmt)
149
- accessible_board_ids = [row[0] for row in accessible_boards_result.all()]
150
-
151
- if not accessible_board_ids:
152
- return []
153
-
154
- generations_query = generations_query.where(
155
- Generations.board_id.in_(accessible_board_ids)
156
- )
157
-
158
- # Apply status filter
159
- if status is not None:
160
- generations_query = generations_query.where(Generations.status == status.value)
161
-
162
- # Apply artifact_type filter
163
- if artifact_type is not None:
164
- generations_query = generations_query.where(
165
- Generations.artifact_type == artifact_type.value
166
- )
167
-
168
- # Order by created_at DESC and apply pagination
169
- generations_query = (
170
- generations_query.order_by(Generations.created_at.desc()).limit(limit).offset(offset)
171
- )
172
-
173
- result = await session.execute(generations_query)
174
- generations = result.scalars().all()
175
-
176
- # Convert to GraphQL types
177
- from ..types.generation import ArtifactType as ArtifactTypeEnum
178
- from ..types.generation import Generation as GenerationType
179
- from ..types.generation import GenerationStatus as GenerationStatusEnum
180
-
181
- return [
182
- GenerationType(
183
- id=gen.id,
184
- tenant_id=gen.tenant_id,
185
- board_id=gen.board_id,
186
- user_id=gen.user_id,
187
- generator_name=gen.generator_name,
188
- artifact_type=ArtifactTypeEnum(gen.artifact_type),
189
- storage_url=gen.storage_url,
190
- thumbnail_url=gen.thumbnail_url,
191
- additional_files=gen.additional_files or [],
192
- input_params=gen.input_params or {},
193
- output_metadata=gen.output_metadata or {},
194
- external_job_id=gen.external_job_id,
195
- status=GenerationStatusEnum(gen.status),
196
- progress=float(gen.progress or 0.0),
197
- error_message=gen.error_message,
198
- started_at=gen.started_at,
199
- completed_at=gen.completed_at,
200
- created_at=gen.created_at,
201
- updated_at=gen.updated_at,
202
- )
203
- for gen in generations
204
- ]
205
-
206
-
207
- # Field resolvers
208
- async def resolve_generation_board(generation: Generation, info: strawberry.Info) -> Board:
209
- """Resolve the board this generation belongs to."""
210
- auth_context = await get_auth_context_from_info(info)
211
-
212
- async with get_async_session() as session:
213
- stmt = (
214
- select(Boards)
215
- .where(Boards.id == generation.board_id)
216
- .options(
217
- selectinload(Boards.owner),
218
- selectinload(Boards.board_members).selectinload(BoardMembers.user),
219
- )
220
- )
221
- result = await session.execute(stmt)
222
- board = result.scalar_one_or_none()
223
-
224
- if not board:
225
- raise RuntimeError("Generation board not found")
226
-
227
- if not can_access_board(board, auth_context):
228
- raise RuntimeError("Access denied to generation board")
229
-
230
- from ..types.board import Board as BoardType
231
-
232
- return BoardType(
233
- id=board.id,
234
- tenant_id=board.tenant_id,
235
- owner_id=board.owner_id,
236
- title=board.title,
237
- description=board.description,
238
- is_public=board.is_public,
239
- settings=board.settings or {},
240
- metadata=board.metadata_ or {},
241
- created_at=board.created_at,
242
- updated_at=board.updated_at,
243
- )
244
-
245
-
246
- async def resolve_generation_user(generation: Generation, info: strawberry.Info) -> User:
247
- """Resolve the user who created this generation."""
248
- async with get_async_session() as session:
249
- stmt = select(Users).where(Users.id == generation.user_id)
250
- result = await session.execute(stmt)
251
- user = result.scalar_one_or_none()
252
-
253
- if not user:
254
- raise RuntimeError("Generation user not found")
255
-
256
- from ..types.user import User as UserType
257
-
258
- return UserType(
259
- id=user.id,
260
- tenant_id=user.tenant_id,
261
- auth_provider=user.auth_provider,
262
- auth_subject=user.auth_subject,
263
- email=user.email,
264
- display_name=user.display_name,
265
- avatar_url=user.avatar_url,
266
- created_at=user.created_at,
267
- updated_at=user.updated_at,
268
- )
269
-
270
-
271
- async def resolve_generation_parent(
272
- generation: Generation, info: strawberry.Info
273
- ) -> Generation | None:
274
- """DEPRECATED: Use ancestry field instead. This resolver is no longer used."""
275
- return None
276
-
277
-
278
- async def resolve_generation_inputs(
279
- generation: Generation, info: strawberry.Info
280
- ) -> list[Generation]: # noqa: E501
281
- """DEPRECATED: Use input_artifacts field instead. This resolver is no longer used."""
282
- return []
283
-
284
-
285
- async def resolve_generation_children(
286
- generation: Generation, info: strawberry.Info
287
- ) -> list[Generation]: # noqa: E501
288
- """DEPRECATED: Use descendants field instead. This resolver is no longer used."""
289
- return []
290
-
291
-
292
- # Mutation resolvers
293
- async def create_generation(info: strawberry.Info, input: CreateGenerationInput) -> Generation:
294
- """
295
- Create a new generation and enqueue it for processing.
296
-
297
- Requires editor or owner role on the target board.
298
- """
299
- auth_context = await get_auth_context_from_info(info)
300
- if not auth_context or not auth_context.is_authenticated or not auth_context.user_id:
301
- raise RuntimeError("Authentication required to create a generation")
302
-
303
- async with get_async_session() as session:
304
- # Check board access - require editor or owner role
305
- board_stmt = (
306
- select(Boards)
307
- .where(Boards.id == input.board_id)
308
- .options(selectinload(Boards.board_members))
309
- )
310
- board_result = await session.execute(board_stmt)
311
- board = board_result.scalar_one_or_none()
312
-
313
- if not board:
314
- raise RuntimeError("Board not found")
315
-
316
- # Check if user is owner or editor
317
- is_owner = board.owner_id == auth_context.user_id
318
- is_editor = any(
319
- member.user_id == auth_context.user_id and member.role in {"editor", "admin"}
320
- for member in board.board_members
321
- )
322
-
323
- if not is_owner and not is_editor:
324
- raise RuntimeError(
325
- "Permission denied: only board owner or editor can create generations"
326
- )
327
-
328
- # Validate generator exists
329
- generator = generator_registry.get(input.generator_name)
330
- if generator is None:
331
- raise RuntimeError(f"Unknown generator: {input.generator_name}")
332
-
333
- # Create generation record
334
- # Note: Lineage will be captured automatically during generation processing
335
- # via artifact resolution in the worker (see actors.py)
336
- gen = await jobs_repo.create_generation(
337
- session,
338
- tenant_id=auth_context.tenant_id,
339
- board_id=input.board_id,
340
- user_id=auth_context.user_id,
341
- generator_name=input.generator_name,
342
- artifact_type=input.artifact_type.value,
343
- input_params=input.input_params,
344
- )
345
-
346
- await session.commit()
347
- await session.refresh(gen)
348
-
349
- logger.info(
350
- "Generation created",
351
- generation_id=str(gen.id),
352
- board_id=str(input.board_id),
353
- user_id=str(auth_context.user_id),
354
- generator_name=input.generator_name,
355
- )
356
-
357
- # Enqueue job for processing
358
- message = process_generation.send(str(gen.id))
359
- logger.info(
360
- "Generation job enqueued",
361
- generation_id=str(gen.id),
362
- message_id=message.message_id,
363
- queue_name=message.queue_name,
364
- )
365
-
366
- # Convert to GraphQL type
367
- from ..types.generation import ArtifactType, GenerationStatus
368
- from ..types.generation import Generation as GenerationType
369
-
370
- return GenerationType(
371
- id=gen.id,
372
- tenant_id=gen.tenant_id,
373
- board_id=gen.board_id,
374
- user_id=gen.user_id,
375
- generator_name=gen.generator_name,
376
- artifact_type=ArtifactType(gen.artifact_type),
377
- storage_url=gen.storage_url,
378
- thumbnail_url=gen.thumbnail_url,
379
- additional_files=gen.additional_files or [],
380
- input_params=gen.input_params or {},
381
- output_metadata=gen.output_metadata or {},
382
- external_job_id=gen.external_job_id,
383
- status=GenerationStatus(gen.status),
384
- progress=float(gen.progress or 0.0),
385
- error_message=gen.error_message,
386
- started_at=gen.started_at,
387
- completed_at=gen.completed_at,
388
- created_at=gen.created_at,
389
- updated_at=gen.updated_at,
390
- )
391
-
392
-
393
- async def cancel_generation(info: strawberry.Info, id: UUID) -> Generation:
394
- """
395
- Cancel a pending or processing generation.
396
-
397
- Requires ownership or editor role on the board.
398
- """
399
- auth_context = await get_auth_context_from_info(info)
400
- if not auth_context or not auth_context.is_authenticated:
401
- raise RuntimeError("Authentication required to cancel a generation")
402
-
403
- async with get_async_session() as session:
404
- # Query generation
405
- stmt = select(Generations).where(Generations.id == id)
406
- result = await session.execute(stmt)
407
- gen = result.scalar_one_or_none()
408
-
409
- if not gen:
410
- raise RuntimeError("Generation not found")
411
-
412
- # Check board access and permissions
413
- board_stmt = (
414
- select(Boards)
415
- .where(Boards.id == gen.board_id)
416
- .options(selectinload(Boards.board_members))
417
- )
418
- board_result = await session.execute(board_stmt)
419
- board = board_result.scalar_one_or_none()
420
-
421
- if not board:
422
- raise RuntimeError("Board not found")
423
-
424
- # Check authorization
425
- is_owner = board.owner_id == auth_context.user_id
426
- is_editor = any(
427
- member.user_id == auth_context.user_id and member.role in {"editor", "admin"}
428
- for member in board.board_members
429
- )
430
- is_creator = gen.user_id == auth_context.user_id
431
-
432
- # Owner can cancel any generation, editor can only cancel their own
433
- if not is_owner and not (is_editor and is_creator):
434
- raise RuntimeError(
435
- "Permission denied: only board owner or generation creator can cancel"
436
- )
437
-
438
- # Validate status
439
- if gen.status not in {"pending", "processing"}:
440
- raise RuntimeError(
441
- f"Cannot cancel generation with status '{gen.status}'. "
442
- "Only pending or processing generations can be cancelled."
443
- )
444
-
445
- # Update status to cancelled
446
- await jobs_repo.update_progress(
447
- session,
448
- id,
449
- status="cancelled",
450
- progress=float(gen.progress or 0.0),
451
- error_message="Cancelled by user",
452
- )
453
- await session.commit()
454
-
455
- # Refresh to get updated data
456
- await session.refresh(gen)
457
-
458
- logger.info(
459
- "Generation cancelled",
460
- generation_id=str(id),
461
- user_id=str(auth_context.user_id),
462
- )
463
-
464
- # Convert to GraphQL type
465
- from ..types.generation import ArtifactType, GenerationStatus
466
- from ..types.generation import Generation as GenerationType
467
-
468
- return GenerationType(
469
- id=gen.id,
470
- tenant_id=gen.tenant_id,
471
- board_id=gen.board_id,
472
- user_id=gen.user_id,
473
- generator_name=gen.generator_name,
474
- artifact_type=ArtifactType(gen.artifact_type),
475
- storage_url=gen.storage_url,
476
- thumbnail_url=gen.thumbnail_url,
477
- additional_files=gen.additional_files or [],
478
- input_params=gen.input_params or {},
479
- output_metadata=gen.output_metadata or {},
480
- external_job_id=gen.external_job_id,
481
- status=GenerationStatus(gen.status),
482
- progress=float(gen.progress or 0.0),
483
- error_message=gen.error_message,
484
- started_at=gen.started_at,
485
- completed_at=gen.completed_at,
486
- created_at=gen.created_at,
487
- updated_at=gen.updated_at,
488
- )
489
-
490
-
491
- async def delete_generation(info: strawberry.Info, id: UUID) -> bool:
492
- """
493
- Delete a generation and its associated storage artifacts.
494
-
495
- Requires ownership or editor role on the board.
496
- """
497
- auth_context = await get_auth_context_from_info(info)
498
- if not auth_context or not auth_context.is_authenticated:
499
- raise RuntimeError("Authentication required to delete a generation")
500
-
501
- async with get_async_session() as session:
502
- # Query generation
503
- stmt = select(Generations).where(Generations.id == id)
504
- result = await session.execute(stmt)
505
- gen = result.scalar_one_or_none()
506
-
507
- if not gen:
508
- raise RuntimeError("Generation not found")
509
-
510
- # Check board access and permissions
511
- board_stmt = (
512
- select(Boards)
513
- .where(Boards.id == gen.board_id)
514
- .options(selectinload(Boards.board_members))
515
- )
516
- board_result = await session.execute(board_stmt)
517
- board = board_result.scalar_one_or_none()
518
-
519
- if not board:
520
- raise RuntimeError("Board not found")
521
-
522
- # Check authorization
523
- is_owner = board.owner_id == auth_context.user_id
524
- is_editor = any(
525
- member.user_id == auth_context.user_id and member.role in {"editor", "admin"}
526
- for member in board.board_members
527
- )
528
- is_creator = gen.user_id == auth_context.user_id
529
-
530
- # Owner can delete any generation, editor can only delete their own
531
- if not is_owner and not (is_editor and is_creator):
532
- raise RuntimeError(
533
- "Permission denied: only board owner or generation creator can delete"
534
- )
535
-
536
- # Delete storage artifacts
537
- # TODO: Full storage deletion requires storage_key and storage_provider fields
538
- # to be added to the Generations table. For now, we log the deletion intent.
539
- # Once those fields are added, use: await storage_manager.delete_artifact(key, provider)
540
-
541
- if gen.storage_url or gen.thumbnail_url or gen.additional_files:
542
- logger.info(
543
- "Storage artifact deletion",
544
- generation_id=str(id),
545
- has_storage_url=bool(gen.storage_url),
546
- has_thumbnail=bool(gen.thumbnail_url),
547
- additional_files_count=(len(gen.additional_files) if gen.additional_files else 0),
548
- )
549
- logger.warning(
550
- "Storage artifact deletion not yet implemented - requires storage_key and "
551
- "storage_provider fields in Generations table",
552
- generation_id=str(id),
553
- )
554
-
555
- # Delete generation from database
556
- await session.delete(gen)
557
- await session.commit()
558
-
559
- logger.info(
560
- "Generation deleted",
561
- generation_id=str(id),
562
- user_id=str(auth_context.user_id),
563
- )
564
-
565
- return True
566
-
567
-
568
- async def regenerate(info: strawberry.Info, id: UUID) -> Generation:
569
- """
570
- Regenerate from an existing generation.
571
-
572
- Creates a new generation with the same inputs and sets the original as parent.
573
- """
574
- auth_context = await get_auth_context_from_info(info)
575
- if not auth_context or not auth_context.is_authenticated or not auth_context.user_id:
576
- raise RuntimeError("Authentication required to regenerate")
577
-
578
- async with get_async_session() as session:
579
- # Query original generation
580
- stmt = select(Generations).where(Generations.id == id)
581
- result = await session.execute(stmt)
582
- original = result.scalar_one_or_none()
583
-
584
- if not original:
585
- raise RuntimeError("Original generation not found")
586
-
587
- # Check board access - require editor or owner role
588
- board_stmt = (
589
- select(Boards)
590
- .where(Boards.id == original.board_id)
591
- .options(selectinload(Boards.board_members))
592
- )
593
- board_result = await session.execute(board_stmt)
594
- board = board_result.scalar_one_or_none()
595
-
596
- if not board:
597
- raise RuntimeError("Board not found")
598
-
599
- # Check if user is owner or editor
600
- is_owner = board.owner_id == auth_context.user_id
601
- is_editor = any(
602
- member.user_id == auth_context.user_id and member.role in {"editor", "admin"}
603
- for member in board.board_members
604
- )
605
-
606
- if not is_owner and not is_editor:
607
- raise RuntimeError("Permission denied: only board owner or editor can regenerate")
608
-
609
- # Validate generator still exists
610
- generator = generator_registry.get(original.generator_name)
611
- if generator is None:
612
- raise RuntimeError(f"Generator '{original.generator_name}' is no longer available")
613
-
614
- # Create new generation with copied inputs
615
- new_gen = await jobs_repo.create_generation(
616
- session,
617
- tenant_id=original.tenant_id,
618
- board_id=original.board_id,
619
- user_id=auth_context.user_id,
620
- generator_name=original.generator_name,
621
- artifact_type=original.artifact_type,
622
- input_params=original.input_params or {},
623
- )
624
-
625
- # Note: Lineage will be captured automatically during generation processing
626
- # via artifact resolution in the worker (see actors.py)
627
-
628
- await session.commit()
629
- await session.refresh(new_gen)
630
-
631
- logger.info(
632
- "Generation regenerated",
633
- new_generation_id=str(new_gen.id),
634
- original_generation_id=str(id),
635
- user_id=str(auth_context.user_id),
636
- )
637
-
638
- # Enqueue job for processing
639
- process_generation.send(str(new_gen.id))
640
- logger.info("Regeneration job enqueued", generation_id=str(new_gen.id))
641
-
642
- # Convert to GraphQL type
643
- from ..types.generation import ArtifactType, GenerationStatus
644
- from ..types.generation import Generation as GenerationType
645
-
646
- return GenerationType(
647
- id=new_gen.id,
648
- tenant_id=new_gen.tenant_id,
649
- board_id=new_gen.board_id,
650
- user_id=new_gen.user_id,
651
- generator_name=new_gen.generator_name,
652
- artifact_type=ArtifactType(new_gen.artifact_type),
653
- storage_url=new_gen.storage_url,
654
- thumbnail_url=new_gen.thumbnail_url,
655
- additional_files=new_gen.additional_files or [],
656
- input_params=new_gen.input_params or {},
657
- output_metadata=new_gen.output_metadata or {},
658
- external_job_id=new_gen.external_job_id,
659
- status=GenerationStatus(new_gen.status),
660
- progress=float(new_gen.progress or 0.0),
661
- error_message=new_gen.error_message,
662
- started_at=new_gen.started_at,
663
- completed_at=new_gen.completed_at,
664
- created_at=new_gen.created_at,
665
- updated_at=new_gen.updated_at,
666
- )