collavre 0.1.0

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 (410) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +221 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/stylesheets/collavre/actiontext.css +577 -0
  5. data/app/assets/stylesheets/collavre/activity_logs.css +99 -0
  6. data/app/assets/stylesheets/collavre/comments_popup.css +692 -0
  7. data/app/assets/stylesheets/collavre/creatives.css +559 -0
  8. data/app/assets/stylesheets/collavre/dark_mode.css +118 -0
  9. data/app/assets/stylesheets/collavre/mention_menu.css +43 -0
  10. data/app/assets/stylesheets/collavre/popup.css +160 -0
  11. data/app/assets/stylesheets/collavre/print.css +37 -0
  12. data/app/assets/stylesheets/collavre/slide_view.css +79 -0
  13. data/app/assets/stylesheets/collavre/user_menu.css +34 -0
  14. data/app/channels/collavre/comments_presence_channel.rb +54 -0
  15. data/app/channels/collavre/slide_view_channel.rb +11 -0
  16. data/app/channels/collavre/topics_channel.rb +12 -0
  17. data/app/components/collavre/avatar_component.html.erb +15 -0
  18. data/app/components/collavre/avatar_component.rb +59 -0
  19. data/app/components/collavre/inbox/badge_component.html.erb +6 -0
  20. data/app/components/collavre/inbox/badge_component.rb +18 -0
  21. data/app/components/collavre/plans_timeline_component.html.erb +14 -0
  22. data/app/components/collavre/plans_timeline_component.rb +56 -0
  23. data/app/components/collavre/popup_menu_component.html.erb +6 -0
  24. data/app/components/collavre/popup_menu_component.rb +30 -0
  25. data/app/components/collavre/progress_filter_component.html.erb +5 -0
  26. data/app/components/collavre/progress_filter_component.rb +10 -0
  27. data/app/components/collavre/user_mention_menu_component.html.erb +3 -0
  28. data/app/components/collavre/user_mention_menu_component.rb +8 -0
  29. data/app/controllers/collavre/application_controller.rb +15 -0
  30. data/app/controllers/collavre/attachments_controller.rb +44 -0
  31. data/app/controllers/collavre/calendar_events_controller.rb +15 -0
  32. data/app/controllers/collavre/comment_read_pointers_controller.rb +80 -0
  33. data/app/controllers/collavre/comments/activity_logs_controller.rb +26 -0
  34. data/app/controllers/collavre/comments/reactions_controller.rb +82 -0
  35. data/app/controllers/collavre/comments_controller.rb +464 -0
  36. data/app/controllers/collavre/contacts_controller.rb +10 -0
  37. data/app/controllers/collavre/creative_expanded_states_controller.rb +27 -0
  38. data/app/controllers/collavre/creative_imports_controller.rb +24 -0
  39. data/app/controllers/collavre/creative_plans_controller.rb +69 -0
  40. data/app/controllers/collavre/creative_shares_controller.rb +79 -0
  41. data/app/controllers/collavre/creatives_controller.rb +535 -0
  42. data/app/controllers/collavre/devices_controller.rb +19 -0
  43. data/app/controllers/collavre/email_verifications_controller.rb +16 -0
  44. data/app/controllers/collavre/emails_controller.rb +11 -0
  45. data/app/controllers/collavre/github_auth_controller.rb +25 -0
  46. data/app/controllers/collavre/google_auth_controller.rb +43 -0
  47. data/app/controllers/collavre/inbox_items_controller.rb +64 -0
  48. data/app/controllers/collavre/invites_controller.rb +27 -0
  49. data/app/controllers/collavre/notion_auth_controller.rb +25 -0
  50. data/app/controllers/collavre/passwords_controller.rb +37 -0
  51. data/app/controllers/collavre/plans_controller.rb +110 -0
  52. data/app/controllers/collavre/sessions_controller.rb +57 -0
  53. data/app/controllers/collavre/topics_controller.rb +58 -0
  54. data/app/controllers/collavre/user_themes_controller.rb +58 -0
  55. data/app/controllers/collavre/users_controller.rb +390 -0
  56. data/app/helpers/collavre/application_helper.rb +4 -0
  57. data/app/helpers/collavre/comments_helper.rb +9 -0
  58. data/app/helpers/collavre/creatives_helper.rb +343 -0
  59. data/app/helpers/collavre/navigation_helper.rb +163 -0
  60. data/app/helpers/collavre/user_themes_helper.rb +4 -0
  61. data/app/javascript/collavre.js +26 -0
  62. data/app/javascript/components/InlineLexicalEditor.jsx +889 -0
  63. data/app/javascript/components/LinkPopup.jsx +112 -0
  64. data/app/javascript/components/creative_tree_row.js +503 -0
  65. data/app/javascript/components/plugins/attachment_cleanup_plugin.jsx +95 -0
  66. data/app/javascript/components/plugins/image_upload_plugin.jsx +162 -0
  67. data/app/javascript/controllers/click_target_controller.js +13 -0
  68. data/app/javascript/controllers/comment_controller.js +162 -0
  69. data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +68 -0
  70. data/app/javascript/controllers/comments/form_controller.js +530 -0
  71. data/app/javascript/controllers/comments/list_controller.js +715 -0
  72. data/app/javascript/controllers/comments/mention_menu_controller.js +41 -0
  73. data/app/javascript/controllers/comments/popup_controller.js +385 -0
  74. data/app/javascript/controllers/comments/presence_controller.js +311 -0
  75. data/app/javascript/controllers/comments/topics_controller.js +338 -0
  76. data/app/javascript/controllers/common_popup_controller.js +55 -0
  77. data/app/javascript/controllers/creatives/drag_drop_controller.js +45 -0
  78. data/app/javascript/controllers/creatives/expansion_controller.js +222 -0
  79. data/app/javascript/controllers/creatives/import_controller.js +116 -0
  80. data/app/javascript/controllers/creatives/row_editor_controller.js +8 -0
  81. data/app/javascript/controllers/creatives/select_mode_controller.js +231 -0
  82. data/app/javascript/controllers/creatives/set_plan_modal_controller.js +107 -0
  83. data/app/javascript/controllers/creatives/tree_controller.js +218 -0
  84. data/app/javascript/controllers/index.js +79 -0
  85. data/app/javascript/controllers/link_creative_controller.js +91 -0
  86. data/app/javascript/controllers/popup_menu_controller.js +82 -0
  87. data/app/javascript/controllers/progress_filter_controller.js +35 -0
  88. data/app/javascript/controllers/reaction_picker_controller.js +107 -0
  89. data/app/javascript/controllers/share_invite_controller.js +15 -0
  90. data/app/javascript/controllers/share_user_search_controller.js +121 -0
  91. data/app/javascript/controllers/tabs_controller.js +43 -0
  92. data/app/javascript/creatives/drag_drop/dom.js +170 -0
  93. data/app/javascript/creatives/drag_drop/event_handlers.js +846 -0
  94. data/app/javascript/creatives/drag_drop/indicator.js +35 -0
  95. data/app/javascript/creatives/drag_drop/operations.js +116 -0
  96. data/app/javascript/creatives/drag_drop/state.js +31 -0
  97. data/app/javascript/creatives/tree_renderer.js +248 -0
  98. data/app/javascript/lib/api/__tests__/queue_manager.test.js +153 -0
  99. data/app/javascript/lib/api/creatives.js +79 -0
  100. data/app/javascript/lib/api/csrf_fetch.js +22 -0
  101. data/app/javascript/lib/api/drag_drop.js +31 -0
  102. data/app/javascript/lib/api/queue_manager.js +423 -0
  103. data/app/javascript/lib/apply_lexical_styles.js +15 -0
  104. data/app/javascript/lib/common_popup.js +195 -0
  105. data/app/javascript/lib/lexical/__tests__/action_text_attachment_node.test.jsx +91 -0
  106. data/app/javascript/lib/lexical/__tests__/attachment_payload.test.js +194 -0
  107. data/app/javascript/lib/lexical/action_text_attachment_node.js +459 -0
  108. data/app/javascript/lib/lexical/attachment_node.jsx +170 -0
  109. data/app/javascript/lib/lexical/attachment_payload.js +293 -0
  110. data/app/javascript/lib/lexical/dom_attachment_utils.js +66 -0
  111. data/app/javascript/lib/lexical/image_node.jsx +159 -0
  112. data/app/javascript/lib/lexical/style_attributes.js +40 -0
  113. data/app/javascript/lib/responsive_images.js +54 -0
  114. data/app/javascript/lib/turbo_stream_actions.js +33 -0
  115. data/app/javascript/lib/utils/markdown.js +23 -0
  116. data/app/javascript/modules/creative_guide.js +53 -0
  117. data/app/javascript/modules/creative_row_editor.js +1841 -0
  118. data/app/javascript/modules/creative_row_swipe.js +43 -0
  119. data/app/javascript/modules/creatives.js +15 -0
  120. data/app/javascript/modules/export_to_markdown.js +34 -0
  121. data/app/javascript/modules/inbox_panel.js +226 -0
  122. data/app/javascript/modules/lexical_inline_editor.jsx +133 -0
  123. data/app/javascript/modules/mention_menu.js +77 -0
  124. data/app/javascript/modules/plans_menu.js +39 -0
  125. data/app/javascript/modules/plans_timeline.js +397 -0
  126. data/app/javascript/modules/share_modal.js +73 -0
  127. data/app/javascript/modules/share_user_popup.js +77 -0
  128. data/app/javascript/modules/slide_view.js +163 -0
  129. data/app/javascript/services/cable.js +32 -0
  130. data/app/javascript/slide_view.js +2 -0
  131. data/app/javascript/utils/caret_position.js +42 -0
  132. data/app/javascript/utils/clipboard.js +40 -0
  133. data/app/jobs/collavre/ai_agent_job.rb +27 -0
  134. data/app/jobs/collavre/inbox_summary_job.rb +24 -0
  135. data/app/jobs/collavre/notion_export_job.rb +30 -0
  136. data/app/jobs/collavre/notion_sync_job.rb +48 -0
  137. data/app/jobs/collavre/permission_cache_cleanup_job.rb +36 -0
  138. data/app/jobs/collavre/permission_cache_job.rb +71 -0
  139. data/app/jobs/collavre/push_notification_job.rb +86 -0
  140. data/app/mailers/collavre/application_mailer.rb +17 -0
  141. data/app/mailers/collavre/creative_mailer.rb +9 -0
  142. data/app/mailers/collavre/email_verification_mailer.rb +20 -0
  143. data/app/mailers/collavre/inbox_mailer.rb +19 -0
  144. data/app/mailers/collavre/invitation_mailer.rb +16 -0
  145. data/app/mailers/collavre/passwords_mailer.rb +10 -0
  146. data/app/models/collavre/activity_log.rb +13 -0
  147. data/app/models/collavre/application_record.rb +5 -0
  148. data/app/models/collavre/calendar_event.rb +20 -0
  149. data/app/models/collavre/comment.rb +307 -0
  150. data/app/models/collavre/comment_presence_store.rb +30 -0
  151. data/app/models/collavre/comment_reaction.rb +11 -0
  152. data/app/models/collavre/comment_read_pointer.rb +26 -0
  153. data/app/models/collavre/contact.rb +23 -0
  154. data/app/models/collavre/creative.rb +413 -0
  155. data/app/models/collavre/creative_expanded_state.rb +11 -0
  156. data/app/models/collavre/creative_share.rb +122 -0
  157. data/app/models/collavre/creative_shares_cache.rb +18 -0
  158. data/app/models/collavre/current.rb +14 -0
  159. data/app/models/collavre/device.rb +13 -0
  160. data/app/models/collavre/email.rb +14 -0
  161. data/app/models/collavre/github_account.rb +10 -0
  162. data/app/models/collavre/github_repository_link.rb +19 -0
  163. data/app/models/collavre/inbox_item.rb +95 -0
  164. data/app/models/collavre/invitation.rb +22 -0
  165. data/app/models/collavre/label.rb +47 -0
  166. data/app/models/collavre/mcp_tool.rb +30 -0
  167. data/app/models/collavre/notion_account.rb +17 -0
  168. data/app/models/collavre/notion_block_link.rb +10 -0
  169. data/app/models/collavre/notion_page_link.rb +19 -0
  170. data/app/models/collavre/plan.rb +20 -0
  171. data/app/models/collavre/session.rb +24 -0
  172. data/app/models/collavre/system_setting.rb +144 -0
  173. data/app/models/collavre/tag.rb +10 -0
  174. data/app/models/collavre/task.rb +10 -0
  175. data/app/models/collavre/task_action.rb +10 -0
  176. data/app/models/collavre/topic.rb +12 -0
  177. data/app/models/collavre/user.rb +174 -0
  178. data/app/models/collavre/user_theme.rb +10 -0
  179. data/app/models/collavre/variation.rb +5 -0
  180. data/app/models/collavre/webauthn_credential.rb +11 -0
  181. data/app/services/collavre/ai_agent_service.rb +193 -0
  182. data/app/services/collavre/ai_client.rb +183 -0
  183. data/app/services/collavre/ai_system_prompt_renderer.rb +38 -0
  184. data/app/services/collavre/auto_theme_generator.rb +198 -0
  185. data/app/services/collavre/comment_link_formatter.rb +60 -0
  186. data/app/services/collavre/comments/action_executor.rb +262 -0
  187. data/app/services/collavre/comments/action_validator.rb +58 -0
  188. data/app/services/collavre/comments/calendar_command.rb +97 -0
  189. data/app/services/collavre/comments/command_processor.rb +37 -0
  190. data/app/services/collavre/comments/mcp_command.rb +109 -0
  191. data/app/services/collavre/comments/mcp_command_builder.rb +32 -0
  192. data/app/services/collavre/creatives/filter_pipeline.rb +196 -0
  193. data/app/services/collavre/creatives/filters/assignee_filter.rb +30 -0
  194. data/app/services/collavre/creatives/filters/base_filter.rb +24 -0
  195. data/app/services/collavre/creatives/filters/comment_filter.rb +21 -0
  196. data/app/services/collavre/creatives/filters/date_filter.rb +58 -0
  197. data/app/services/collavre/creatives/filters/progress_filter.rb +25 -0
  198. data/app/services/collavre/creatives/filters/search_filter.rb +28 -0
  199. data/app/services/collavre/creatives/filters/tag_filter.rb +16 -0
  200. data/app/services/collavre/creatives/importer.rb +47 -0
  201. data/app/services/collavre/creatives/index_query.rb +191 -0
  202. data/app/services/collavre/creatives/path_exporter.rb +131 -0
  203. data/app/services/collavre/creatives/permission_cache_builder.rb +194 -0
  204. data/app/services/collavre/creatives/permission_checker.rb +42 -0
  205. data/app/services/collavre/creatives/plan_tagger.rb +53 -0
  206. data/app/services/collavre/creatives/progress_service.rb +89 -0
  207. data/app/services/collavre/creatives/reorderer.rb +187 -0
  208. data/app/services/collavre/creatives/tree_builder.rb +231 -0
  209. data/app/services/collavre/creatives/tree_formatter.rb +36 -0
  210. data/app/services/collavre/gemini_parent_recommender.rb +77 -0
  211. data/app/services/collavre/github/client.rb +112 -0
  212. data/app/services/collavre/github/pull_request_analyzer.rb +280 -0
  213. data/app/services/collavre/github/pull_request_processor.rb +181 -0
  214. data/app/services/collavre/github/webhook_provisioner.rb +130 -0
  215. data/app/services/collavre/google_calendar_service.rb +149 -0
  216. data/app/services/collavre/link_preview_fetcher.rb +230 -0
  217. data/app/services/collavre/markdown_importer.rb +202 -0
  218. data/app/services/collavre/mcp_service.rb +217 -0
  219. data/app/services/collavre/notion_client.rb +231 -0
  220. data/app/services/collavre/notion_creative_exporter.rb +296 -0
  221. data/app/services/collavre/notion_service.rb +249 -0
  222. data/app/services/collavre/ppt_importer.rb +76 -0
  223. data/app/services/collavre/ruby_llm_interaction_logger.rb +34 -0
  224. data/app/services/collavre/system_events/context_builder.rb +41 -0
  225. data/app/services/collavre/system_events/dispatcher.rb +19 -0
  226. data/app/services/collavre/system_events/router.rb +72 -0
  227. data/app/services/collavre/tools/creative_retrieval_service.rb +138 -0
  228. data/app/views/admin/shared/_tabs.html.erb +4 -0
  229. data/app/views/collavre/comments/_activity_log_details.html.erb +23 -0
  230. data/app/views/collavre/comments/_comment.html.erb +147 -0
  231. data/app/views/collavre/comments/_comments_popup.html.erb +46 -0
  232. data/app/views/collavre/comments/_list.html.erb +10 -0
  233. data/app/views/collavre/comments/_presence_avatars.html.erb +8 -0
  234. data/app/views/collavre/comments/_reaction_picker.html.erb +15 -0
  235. data/app/views/collavre/comments/_read_receipts.html.erb +19 -0
  236. data/app/views/collavre/creatives/_add_button.html.erb +20 -0
  237. data/app/views/collavre/creatives/_delete_button.html.erb +12 -0
  238. data/app/views/collavre/creatives/_github_integration_modal.html.erb +77 -0
  239. data/app/views/collavre/creatives/_import_upload_zone.html.erb +10 -0
  240. data/app/views/collavre/creatives/_inline_edit_form.html.erb +89 -0
  241. data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +24 -0
  242. data/app/views/collavre/creatives/_notion_integration_modal.html.erb +90 -0
  243. data/app/views/collavre/creatives/_set_plan_modal.html.erb +25 -0
  244. data/app/views/collavre/creatives/_share_button.html.erb +55 -0
  245. data/app/views/collavre/creatives/edit.html.erb +4 -0
  246. data/app/views/collavre/creatives/index.html.erb +192 -0
  247. data/app/views/collavre/creatives/new.html.erb +4 -0
  248. data/app/views/collavre/creatives/show.html.erb +29 -0
  249. data/app/views/collavre/creatives/slide_view.html.erb +20 -0
  250. data/app/views/collavre/email_verification_mailer/verify.html.erb +4 -0
  251. data/app/views/collavre/email_verification_mailer/verify.text.erb +2 -0
  252. data/app/views/collavre/emails/index.html.erb +19 -0
  253. data/app/views/collavre/emails/show.html.erb +5 -0
  254. data/app/views/collavre/inbox_items/_item.html.erb +18 -0
  255. data/app/views/collavre/inbox_items/_items.html.erb +3 -0
  256. data/app/views/collavre/inbox_items/_list.html.erb +7 -0
  257. data/app/views/collavre/inbox_items/index.html.erb +1 -0
  258. data/app/views/collavre/inbox_mailer/daily_summary.html.erb +11 -0
  259. data/app/views/collavre/inbox_mailer/daily_summary.text.erb +8 -0
  260. data/app/views/collavre/invitation_mailer/invite.html.erb +5 -0
  261. data/app/views/collavre/invitation_mailer/invite.text.erb +3 -0
  262. data/app/views/collavre/invites/show.html.erb +8 -0
  263. data/app/views/collavre/passwords/edit.html.erb +12 -0
  264. data/app/views/collavre/passwords/new.html.erb +17 -0
  265. data/app/views/collavre/passwords_mailer/reset.html.erb +4 -0
  266. data/app/views/collavre/passwords_mailer/reset.text.erb +2 -0
  267. data/app/views/collavre/sessions/new.html.erb +27 -0
  268. data/app/views/collavre/sessions/providers/_google.html.erb +7 -0
  269. data/app/views/collavre/sessions/providers/_passkey.html.erb +3 -0
  270. data/app/views/collavre/shared/_link_creative_modal.html.erb +6 -0
  271. data/app/views/collavre/shared/_navigation.html.erb +37 -0
  272. data/app/views/collavre/shared/navigation/_help_button.html.erb +1 -0
  273. data/app/views/collavre/shared/navigation/_inbox_button.html.erb +5 -0
  274. data/app/views/collavre/shared/navigation/_mobile_inbox_button.html.erb +3 -0
  275. data/app/views/collavre/shared/navigation/_mobile_plans_button.html.erb +1 -0
  276. data/app/views/collavre/shared/navigation/_panels.html.erb +18 -0
  277. data/app/views/collavre/shared/navigation/_plans_button.html.erb +3 -0
  278. data/app/views/collavre/shared/navigation/_search_form.html.erb +6 -0
  279. data/app/views/collavre/user_themes/index.html.erb +75 -0
  280. data/app/views/collavre/users/_app_header.html.erb +5 -0
  281. data/app/views/collavre/users/_contact_management.html.erb +77 -0
  282. data/app/views/collavre/users/_id_pwd_fields.html.erb +27 -0
  283. data/app/views/collavre/users/edit_ai.html.erb +79 -0
  284. data/app/views/collavre/users/edit_password.html.erb +27 -0
  285. data/app/views/collavre/users/index.html.erb +80 -0
  286. data/app/views/collavre/users/new.html.erb +33 -0
  287. data/app/views/collavre/users/new_ai.html.erb +87 -0
  288. data/app/views/collavre/users/passkeys.html.erb +43 -0
  289. data/app/views/collavre/users/show.html.erb +143 -0
  290. data/app/views/inbox/badge_component/_count.html.erb +2 -0
  291. data/app/views/layouts/collavre/slide.html.erb +29 -0
  292. data/config/locales/comments.en.yml +114 -0
  293. data/config/locales/comments.ko.yml +110 -0
  294. data/config/locales/contacts.en.yml +16 -0
  295. data/config/locales/contacts.ko.yml +16 -0
  296. data/config/locales/creatives.en.yml +183 -0
  297. data/config/locales/creatives.ko.yml +164 -0
  298. data/config/locales/invites.en.yml +19 -0
  299. data/config/locales/invites.ko.yml +17 -0
  300. data/config/locales/notifications.en.yml +8 -0
  301. data/config/locales/notifications.ko.yml +8 -0
  302. data/config/locales/plans.en.yml +12 -0
  303. data/config/locales/plans.ko.yml +19 -0
  304. data/config/locales/themes.en.yml +29 -0
  305. data/config/locales/themes.ko.yml +27 -0
  306. data/config/locales/users.en.yml +151 -0
  307. data/config/locales/users.ko.yml +146 -0
  308. data/config/routes.rb +92 -0
  309. data/db/migrate/20241201000000_create_notion_integrations.rb +29 -0
  310. data/db/migrate/20250128110017_create_creatives.rb +11 -0
  311. data/db/migrate/20250128120122_create_users.rb +11 -0
  312. data/db/migrate/20250128120123_create_sessions.rb +11 -0
  313. data/db/migrate/20250128123633_create_subscribers.rb +10 -0
  314. data/db/migrate/20250312000000_create_notion_block_links.rb +16 -0
  315. data/db/migrate/20250312010000_allow_multiple_notion_blocks_per_creative.rb +5 -0
  316. data/db/migrate/20250522115048_remove_name_from_creatives.rb +5 -0
  317. data/db/migrate/20250522190651_add_parent_id_to_creatives.rb +5 -0
  318. data/db/migrate/20250523133100_rename_inventory_count_to_progress.rb +13 -0
  319. data/db/migrate/20250523133101_add_sequence_to_creatives.rb +5 -0
  320. data/db/migrate/20250525205100_add_user_id_to_creatives.rb +10 -0
  321. data/db/migrate/20250527014217_create_creative_shares.rb +12 -0
  322. data/db/migrate/20250528142349_add_origin_id_to_creatives.rb +5 -0
  323. data/db/migrate/20250530060200_create_tags.rb +9 -0
  324. data/db/migrate/20250531105150_add_value_to_tags.rb +5 -0
  325. data/db/migrate/20250531140142_create_labels.rb +12 -0
  326. data/db/migrate/20250531140145_change_tag_to_label_reference.rb +6 -0
  327. data/db/migrate/20250601000000_create_comments.rb +10 -0
  328. data/db/migrate/20250601061830_drop_plans_and_variations.rb +6 -0
  329. data/db/migrate/20250604122600_add_owner_to_labels.rb +5 -0
  330. data/db/migrate/20250606000000_rename_email_address_to_email_in_users.rb +7 -0
  331. data/db/migrate/20250606150329_create_creative_hierarchies.rb +16 -0
  332. data/db/migrate/20250610142000_create_creative_expanded_states.rb +11 -0
  333. data/db/migrate/20250611030138_create_invitations.rb +15 -0
  334. data/db/migrate/20250611105524_create_inbox_items.rb +14 -0
  335. data/db/migrate/20250612150000_update_permissions.rb +43 -0
  336. data/db/migrate/20250612232913_add_email_verified_at_to_users.rb +5 -0
  337. data/db/migrate/20250616065905_add_avatar_to_users.rb +5 -0
  338. data/db/migrate/20250617092111_add_display_level_to_users.rb +5 -0
  339. data/db/migrate/20250620004558_create_emails.rb +13 -0
  340. data/db/migrate/20250621000000_create_comment_read_pointers.rb +11 -0
  341. data/db/migrate/20250622000000_add_completion_mark_to_users.rb +5 -0
  342. data/db/migrate/20250623000000_add_theme_to_users.rb +5 -0
  343. data/db/migrate/20250624000000_add_name_to_users.rb +18 -0
  344. data/db/migrate/20250714190000_create_devices.rb +17 -0
  345. data/db/migrate/20250715120000_add_notifications_enabled_to_users.rb +5 -0
  346. data/db/migrate/20250823000000_add_no_access_permission.rb +25 -0
  347. data/db/migrate/20250826000000_add_calendar_id_to_users.rb +5 -0
  348. data/db/migrate/20250827000000_add_google_oauth_tokens_to_users.rb +8 -0
  349. data/db/migrate/20250827061238_add_timezone_to_users.rb +5 -0
  350. data/db/migrate/20250828000000_create_calendar_events.rb +16 -0
  351. data/db/migrate/20250828060000_add_creative_to_calendar_events.rb +5 -0
  352. data/db/migrate/20250830141052_add_locale_to_users.rb +5 -0
  353. data/db/migrate/20250830141101_add_message_key_to_inbox_items.rb +6 -0
  354. data/db/migrate/20250902025423_remove_description_and_featured_image_from_creatives.rb +6 -0
  355. data/db/migrate/20250910000000_add_private_to_comments.rb +5 -0
  356. data/db/migrate/20250910105640_migrate_write_permissions_to_admin.rb +17 -0
  357. data/db/migrate/20250911084338_backfill_creative_in_comment_inbox_items.rb +27 -0
  358. data/db/migrate/20250923002959_deduplicate_device_fcm_tokens.rb +25 -0
  359. data/db/migrate/20250924000000_add_system_admin_to_users.rb +6 -0
  360. data/db/migrate/20250925000000_create_github_integrations.rb +26 -0
  361. data/db/migrate/20250927000000_add_webhook_secret_to_github_repository_links.rb +29 -0
  362. data/db/migrate/20250928000000_add_action_and_approver_to_comments.rb +6 -0
  363. data/db/migrate/20250928010000_add_action_execution_tracking_to_comments.rb +6 -0
  364. data/db/migrate/20250928105957_add_github_gemini_prompt_to_creatives.rb +5 -0
  365. data/db/migrate/20250929000000_add_comment_and_creative_refs_to_inbox_items.rb +6 -0
  366. data/db/migrate/20251001000001_create_contacts.rb +71 -0
  367. data/db/migrate/20251002000000_add_shared_by_to_creative_shares.rb +14 -0
  368. data/db/migrate/20251124120902_add_ai_fields_to_users.rb +7 -0
  369. data/db/migrate/20251124122218_add_created_by_id_to_users.rb +5 -0
  370. data/db/migrate/20251124124521_add_llm_api_key_to_users.rb +5 -0
  371. data/db/migrate/20251124130000_add_searchable_to_users.rb +6 -0
  372. data/db/migrate/20251125072705_migrate_linked_creative_children_to_origin.rb +28 -0
  373. data/db/migrate/20251126040752_add_description_to_creatives.rb +75 -0
  374. data/db/migrate/20251127000000_move_comments_from_linked_creatives_to_origins.rb +18 -0
  375. data/db/migrate/20251201073823_add_tools_to_users.rb +5 -0
  376. data/db/migrate/20251202062715_create_mcp_tools.rb +16 -0
  377. data/db/migrate/20251204125754_create_tasks_and_task_actions.rb +23 -0
  378. data/db/migrate/20251204125756_add_routing_expression_to_users.rb +5 -0
  379. data/db/migrate/20251204161133_set_default_routing_expression_for_ai_agents.rb +13 -0
  380. data/db/migrate/20251211033025_nullify_self_referencing_origins.rb +9 -0
  381. data/db/migrate/20251211080040_create_topics_and_add_topic_to_comments.rb +15 -0
  382. data/db/migrate/20251215143500_create_activity_logs.rb +16 -0
  383. data/db/migrate/20251222125727_create_webauthn_credentials.rb +14 -0
  384. data/db/migrate/20251222125839_add_webauthn_id_to_users.rb +5 -0
  385. data/db/migrate/20251223022315_create_comment_reactions.rb +13 -0
  386. data/db/migrate/20251223072625_create_user_themes.rb +11 -0
  387. data/db/migrate/20251230074456_add_creative_id_to_labels.rb +5 -0
  388. data/db/migrate/20251230113607_refactor_labels.rb +38 -0
  389. data/db/migrate/20251231010012_backfill_tags_for_labels.rb +15 -0
  390. data/db/migrate/20251231013234_drop_subscribers.rb +9 -0
  391. data/db/migrate/20260106090544_allow_null_user_id_in_creative_shares.rb +6 -0
  392. data/db/migrate/20260106160643_create_system_settings.rb +11 -0
  393. data/db/migrate/20260116000000_create_creative_shares_cache.rb +15 -0
  394. data/db/migrate/20260116000001_populate_creative_shares_cache.rb +23 -0
  395. data/db/migrate/20260119022933_make_source_share_id_nullable_in_creative_shares_caches.rb +7 -0
  396. data/db/migrate/20260119023446_populate_owner_cache_entries.rb +25 -0
  397. data/db/migrate/20260119100000_add_account_lockout_to_users.rb +6 -0
  398. data/db/migrate/20260119110000_add_last_active_at_to_sessions.rb +6 -0
  399. data/db/migrate/20260120045354_encrypt_oauth_tokens.rb +60 -0
  400. data/db/migrate/20260120162259_remove_fk_from_creative_shares_caches.rb +7 -0
  401. data/db/migrate/20260120163856_remove_timestamps_from_creative_shares_caches.rb +6 -0
  402. data/lib/collavre/configuration.rb +14 -0
  403. data/lib/collavre/engine.rb +77 -0
  404. data/lib/collavre/user_extensions.rb +29 -0
  405. data/lib/collavre/version.rb +3 -0
  406. data/lib/collavre.rb +26 -0
  407. data/lib/generators/collavre/install/install_generator.rb +105 -0
  408. data/lib/generators/collavre/install/templates/build.cjs.tt +100 -0
  409. data/lib/tasks/collavre_assets.rake +15 -0
  410. metadata +591 -0
@@ -0,0 +1,18 @@
1
+ class AddNameToUsers < ActiveRecord::Migration[7.1]
2
+ class User < ActiveRecord::Base; end
3
+
4
+ def up
5
+ add_column :users, :name, :string
6
+
7
+ User.reset_column_information
8
+ User.find_each do |user|
9
+ user.update_columns(name: user.email.to_s.split('@').first)
10
+ end
11
+
12
+ change_column_null :users, :name, false
13
+ end
14
+
15
+ def down
16
+ remove_column :users, :name
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ class CreateDevices < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :devices do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :client_id, null: false
6
+ t.integer :device_type, null: false
7
+ t.string :app_id
8
+ t.string :app_version
9
+ t.string :fcm_token, null: false
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :devices, :client_id, unique: true
15
+ add_index :devices, :fcm_token
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ class AddNotificationsEnabledToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :notifications_enabled, :boolean
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ class AddNoAccessPermission < ActiveRecord::Migration[8.0]
2
+ def up
3
+ execute <<~SQL
4
+ UPDATE creative_shares
5
+ SET permission = permission + 1
6
+ SQL
7
+ execute <<~SQL
8
+ UPDATE invitations
9
+ SET permission = permission + 1
10
+ WHERE permission IS NOT NULL
11
+ SQL
12
+ end
13
+
14
+ def down
15
+ execute <<~SQL
16
+ UPDATE creative_shares
17
+ SET permission = permission - 1
18
+ SQL
19
+ execute <<~SQL
20
+ UPDATE invitations
21
+ SET permission = permission - 1
22
+ WHERE permission IS NOT NULL
23
+ SQL
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ class AddCalendarIdToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :calendar_id, :string
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class AddGoogleOauthTokensToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :google_uid, :string
4
+ add_column :users, :google_access_token, :string
5
+ add_column :users, :google_refresh_token, :string
6
+ add_column :users, :google_token_expires_at, :datetime
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddTimezoneToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :timezone, :string
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ class CreateCalendarEvents < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :calendar_events do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :google_event_id, null: false
6
+ t.string :summary
7
+ t.datetime :start_time, null: false
8
+ t.datetime :end_time, null: false
9
+ t.string :html_link
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :calendar_events, :google_event_id, unique: true
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ class AddCreativeToCalendarEvents < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_reference :calendar_events, :creative, foreign_key: true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddLocaleToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :locale, :string
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddMessageKeyToInboxItems < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :inbox_items, :message_key, :string
4
+ add_column :inbox_items, :message_params, :jsonb, default: {}, null: false
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class RemoveDescriptionAndFeaturedImageFromCreatives < ActiveRecord::Migration[8.0]
2
+ def change
3
+ remove_column :creatives, :description, :text
4
+ remove_column :creatives, :featured_image, :string
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddPrivateToComments < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :comments, :private, :boolean, default: false, null: false
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ class MigrateWritePermissionsToAdmin < ActiveRecord::Migration[8.0]
2
+ def up
3
+ write = CreativeShare.permissions[:write]
4
+ admin = CreativeShare.permissions[:admin]
5
+
6
+ CreativeShare.where(permission: write).update_all(permission: admin)
7
+ Invitation.where(permission: write).update_all(permission: admin)
8
+ end
9
+
10
+ def down
11
+ write = CreativeShare.permissions[:write]
12
+ admin = CreativeShare.permissions[:admin]
13
+
14
+ CreativeShare.where(permission: admin).update_all(permission: write)
15
+ Invitation.where(permission: admin).update_all(permission: write)
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ class BackfillCreativeInCommentInboxItems < ActiveRecord::Migration[8.0]
2
+ disable_ddl_transaction!
3
+
4
+ def up
5
+ InboxItem.where(message_key: [ "inbox.comment_added", "inbox.user_mentioned" ]).find_each do |item|
6
+ params = item.message_params || {}
7
+ next if params["creative"].present?
8
+
9
+ comment_id = item.link.to_s[%r{/comments/(\d+)}, 1]
10
+ next unless comment_id
11
+
12
+ comment = Comment.find_by(id: comment_id)
13
+ if comment.blank? || comment.creative.blank?
14
+ params["creative"] = ""
15
+ else
16
+ snippet = comment.creative_snippet
17
+ params["creative"] = snippet
18
+ end
19
+
20
+ item.update_columns(message_params: params)
21
+ end
22
+ end
23
+
24
+ def down
25
+ # no-op
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ class DeduplicateDeviceFcmTokens < ActiveRecord::Migration[8.0]
2
+ class DeviceRecord < ApplicationRecord
3
+ self.table_name = "devices"
4
+ end
5
+
6
+ def up
7
+ DeviceRecord.reset_column_information
8
+
9
+ DeviceRecord.group(:fcm_token).having("COUNT(*) > 1").pluck(:fcm_token).each do |token|
10
+ ids = DeviceRecord.where(fcm_token: token).order(updated_at: :desc, id: :desc).pluck(:id)
11
+ duplicate_ids = ids.drop(1)
12
+ next if duplicate_ids.empty?
13
+
14
+ DeviceRecord.where(id: duplicate_ids).delete_all
15
+ end
16
+
17
+ remove_index :devices, :fcm_token if index_exists?(:devices, :fcm_token)
18
+ add_index :devices, :fcm_token, unique: true
19
+ end
20
+
21
+ def down
22
+ remove_index :devices, :fcm_token if index_exists?(:devices, :fcm_token)
23
+ add_index :devices, :fcm_token unless index_exists?(:devices, :fcm_token)
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ class AddSystemAdminToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :system_admin, :boolean, null: false, default: false
4
+ add_index :users, :system_admin
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ class CreateGithubIntegrations < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :github_accounts do |t|
4
+ t.references :user, null: false, foreign_key: true, index: { unique: true }
5
+ t.string :github_uid, null: false
6
+ t.string :login, null: false
7
+ t.string :name
8
+ t.string :avatar_url
9
+ t.string :token, null: false
10
+ t.datetime :token_expires_at
11
+ t.timestamps
12
+ end
13
+
14
+ create_table :github_repository_links do |t|
15
+ t.references :creative, null: false, foreign_key: true
16
+ t.references :github_account, null: false, foreign_key: true
17
+ t.bigint :repository_id
18
+ t.string :repository_full_name, null: false
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :github_accounts, :github_uid, unique: true
23
+ add_index :github_repository_links, :repository_full_name
24
+ add_index :github_repository_links, [ :creative_id, :repository_full_name ], unique: true, name: "index_github_links_on_creative_and_repo"
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ class AddWebhookSecretToGithubRepositoryLinks < ActiveRecord::Migration[8.0]
2
+ class GithubRepositoryLink < ApplicationRecord
3
+ self.table_name = "github_repository_links"
4
+ end
5
+
6
+ def up
7
+ add_column :github_repository_links, :webhook_secret, :string
8
+
9
+ GithubRepositoryLink.reset_column_information
10
+
11
+ say_with_time "Backfilling webhook secrets" do
12
+ GithubRepositoryLink.find_each do |link|
13
+ link.update_columns(webhook_secret: generate_secret)
14
+ end
15
+ end
16
+
17
+ change_column_null :github_repository_links, :webhook_secret, false
18
+ end
19
+
20
+ def down
21
+ remove_column :github_repository_links, :webhook_secret
22
+ end
23
+
24
+ private
25
+
26
+ def generate_secret
27
+ SecureRandom.hex(20)
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ class AddActionAndApproverToComments < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :comments, :action, :text
4
+ add_reference :comments, :approver, foreign_key: { to_table: :users }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class AddActionExecutionTrackingToComments < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :comments, :action_executed_at, :datetime
4
+ add_reference :comments, :action_executed_by, foreign_key: { to_table: :users }
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddGithubGeminiPromptToCreatives < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :creatives, :github_gemini_prompt, :text
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddCommentAndCreativeRefsToInboxItems < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_reference :inbox_items, :comment, foreign_key: { on_delete: :nullify }
4
+ add_reference :inbox_items, :creative, foreign_key: { on_delete: :nullify }
5
+ end
6
+ end
@@ -0,0 +1,71 @@
1
+ require "set"
2
+
3
+ class CreateContacts < ActiveRecord::Migration[8.0]
4
+ disable_ddl_transaction!
5
+
6
+ def up
7
+ create_table :contacts do |t|
8
+ t.references :user, null: false, foreign_key: true
9
+ t.references :contact_user, null: false, foreign_key: { to_table: :users }
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :contacts, [ :user_id, :contact_user_id ], unique: true
15
+
16
+ backfill_contacts
17
+ end
18
+
19
+ def down
20
+ drop_table :contacts
21
+ end
22
+
23
+ private
24
+
25
+ def backfill_contacts
26
+ say_with_time "Backfilling contacts from invitations and shares" do
27
+ contact_rows = []
28
+ timestamp = Time.current
29
+
30
+ existing_contacts = Set.new
31
+
32
+ invitation_pairs.each do |inviter_id, invitee_id|
33
+ next if inviter_id == invitee_id
34
+ key = [ inviter_id, invitee_id ]
35
+ next if existing_contacts.include?(key)
36
+ existing_contacts << key
37
+ contact_rows << { user_id: inviter_id, contact_user_id: invitee_id, created_at: timestamp, updated_at: timestamp }
38
+ end
39
+
40
+ share_pairs.each do |owner_id, shared_user_id|
41
+ next if owner_id == shared_user_id
42
+ key = [ owner_id, shared_user_id ]
43
+ next if existing_contacts.include?(key)
44
+ existing_contacts << key
45
+ contact_rows << { user_id: owner_id, contact_user_id: shared_user_id, created_at: timestamp, updated_at: timestamp }
46
+ end
47
+
48
+ return if contact_rows.empty?
49
+
50
+ MigrationContact.insert_all(contact_rows)
51
+ end
52
+ end
53
+
54
+ def invitation_pairs
55
+ Invitation
56
+ .joins("INNER JOIN users ON LOWER(users.email) = LOWER(invitations.email)")
57
+ .pluck(:inviter_id, "users.id")
58
+ end
59
+
60
+ def share_pairs
61
+ no_access = CreativeShare.permissions[:no_access]
62
+ CreativeShare
63
+ .joins(:creative)
64
+ .where.not(permission: no_access)
65
+ .pluck("creatives.user_id", :user_id)
66
+ end
67
+
68
+ class MigrationContact < ActiveRecord::Base
69
+ self.table_name = "contacts"
70
+ end
71
+ end
@@ -0,0 +1,14 @@
1
+ class AddSharedByToCreativeShares < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_reference :creative_shares, :shared_by, foreign_key: { to_table: :users, on_delete: :nullify }
4
+
5
+ reversible do |dir|
6
+ dir.up do
7
+ CreativeShare.reset_column_information
8
+ CreativeShare.includes(:creative).find_each do |share|
9
+ share.update_column(:shared_by_id, share.creative.user_id)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ class AddAiFieldsToUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :users, :system_prompt, :text
4
+ add_column :users, :llm_vendor, :string
5
+ add_column :users, :llm_model, :string
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddCreatedByIdToUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :users, :created_by_id, :integer
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddLlmApiKeyToUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :users, :llm_api_key, :string
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddSearchableToUsers < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :users, :searchable, :boolean, default: false, null: false
4
+ add_index :users, :searchable
5
+ end
6
+ end
@@ -0,0 +1,28 @@
1
+ class MigrateLinkedCreativeChildrenToOrigin < ActiveRecord::Migration[8.1]
2
+ def up
3
+ # Find all creatives that are children of a Linked Creative (a creative with an origin_id)
4
+ # and update their parent to be the origin of that Linked Creative.
5
+
6
+ # We use find_each to avoid loading all records into memory
7
+ Creative.where(parent_id: Creative.where.not(origin_id: nil).select(:id)).find_each do |child|
8
+ # child.parent is the Linked Creative
9
+ # child.parent.origin is the Origin Creative
10
+ # We want child.parent to become child.parent.origin
11
+
12
+ # Note: We use update_column to avoid triggering callbacks/validations if necessary,
13
+ # but update! is safer if we want to ensure data integrity.
14
+ # However, since we added a callback that does exactly this, saving might trigger it anyway.
15
+ # Let's use update_columns for speed and to bypass the callback we just added (though it would do the same thing).
16
+
17
+ if child.parent && child.parent.origin
18
+ child.update_columns(parent_id: child.parent.origin_id)
19
+ end
20
+ end
21
+ end
22
+
23
+ def down
24
+ # This migration is irreversible because we lose the information of which Linked Creative
25
+ # the child was originally attached to.
26
+ raise ActiveRecord::IrreversibleMigration
27
+ end
28
+ end
@@ -0,0 +1,75 @@
1
+ class AddDescriptionToCreatives < ActiveRecord::Migration[8.1]
2
+ def up
3
+ add_column :creatives, :description, :text, limit: 4294967295
4
+
5
+ # Data Migration
6
+ say_with_time "Migrating ActionText to description column" do
7
+ Creative.reset_column_information
8
+ Creative.find_each do |creative|
9
+ rich_text = ActionText::RichText.find_by(record_type: "Creative", record_id: creative.id, name: "description")
10
+ if rich_text
11
+ new_description = convert_action_text_content(rich_text.body.to_html)
12
+ creative.update_column(:description, new_description)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def down
19
+ remove_column :creatives, :description
20
+ end
21
+
22
+ private
23
+
24
+ def convert_action_text_content(html)
25
+ return "" if html.blank?
26
+
27
+ doc = Nokogiri::HTML::DocumentFragment.parse(html)
28
+
29
+ doc.css("action-text-attachment").each do |node|
30
+ sgid = node["sgid"]
31
+ content_type = node["content-type"]
32
+ url = node["url"]
33
+ filename = node["filename"]
34
+ width = node["width"]
35
+ height = node["height"]
36
+ caption = node["caption"]
37
+
38
+ blob = nil
39
+ begin
40
+ blob = ActionText::Attachable.from_attachable_sgid(sgid)
41
+ rescue => e
42
+ puts "Error locating blob for sgid #{sgid}: #{e.message}"
43
+ end
44
+
45
+ if blob
46
+ url = Rails.application.routes.url_helpers.rails_blob_path(blob, only_path: true)
47
+ filename = blob.filename.to_s
48
+ content_type = blob.content_type
49
+ end
50
+
51
+ if url.present?
52
+ if content_type&.start_with?("image/")
53
+ img = Nokogiri::XML::Node.new("img", doc)
54
+ img["src"] = url
55
+ img["alt"] = caption || filename
56
+ img["width"] = width if width
57
+ img["height"] = height if height
58
+ node.replace(img)
59
+ else
60
+ a = Nokogiri::XML::Node.new("a", doc)
61
+ a["href"] = url
62
+ a["target"] = "_blank"
63
+ a.content = caption || filename || "Attachment"
64
+ node.replace(a)
65
+ end
66
+ else
67
+ # If no URL found, keep the original node or maybe remove it?
68
+ # Keeping it might be safer but it won't render.
69
+ # Let's leave it as is if we can't transform it.
70
+ end
71
+ end
72
+
73
+ doc.to_html
74
+ end
75
+ end
@@ -0,0 +1,18 @@
1
+ class MoveCommentsFromLinkedCreativesToOrigins < ActiveRecord::Migration[8.1]
2
+ def up
3
+ say_with_time "Reassigning comments from linked creatives to their origins" do
4
+ execute <<~SQL.squish
5
+ UPDATE comments
6
+ SET creative_id = creatives.origin_id
7
+ FROM creatives
8
+ WHERE comments.creative_id = creatives.id
9
+ AND creatives.origin_id IS NOT NULL
10
+ AND comments.creative_id <> creatives.origin_id
11
+ SQL
12
+ end
13
+ end
14
+
15
+ def down
16
+ raise ActiveRecord::IrreversibleMigration
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ class AddToolsToUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :users, :tools, :json
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ class CreateMcpTools < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :mcp_tools do |t|
4
+ t.references :creative, null: false, foreign_key: true
5
+ t.string :name, null: false
6
+ t.text :description
7
+ t.text :source_code
8
+ t.json :definition, default: {}
9
+ t.string :checksum
10
+ t.datetime :approved_at
11
+
12
+ t.timestamps
13
+ end
14
+ add_index :mcp_tools, :name, unique: true
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ class CreateTasksAndTaskActions < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :tasks do |t|
4
+ t.string :name
5
+ t.string :status, default: "pending"
6
+ t.string :trigger_event_name
7
+ t.json :trigger_event_payload
8
+ t.references :agent, null: false, foreign_key: { to_table: :users }
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ create_table :task_actions do |t|
14
+ t.references :task, null: false, foreign_key: true
15
+ t.string :action_type
16
+ t.json :payload
17
+ t.string :status, default: "pending"
18
+ t.json :result
19
+
20
+ t.timestamps
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ class AddRoutingExpressionToUsers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :users, :routing_expression, :text
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class SetDefaultRoutingExpressionForAiAgents < ActiveRecord::Migration[8.1]
2
+ def up
3
+ # Find all AI agents (users with llm_vendor set) that have no routing expression
4
+ User.where.not(llm_vendor: nil).where(routing_expression: [ nil, "" ]).find_each do |user|
5
+ user.update_columns(routing_expression: "chat.mentioned_user.id == agent.id")
6
+ end
7
+ end
8
+
9
+ def down
10
+ # No-op or we could revert, but it's hard to know which ones were changed.
11
+ # Generally data migrations like this don't need a strict down if they are just filling defaults.
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ class NullifySelfReferencingOrigins < ActiveRecord::Migration[8.1]
2
+ def up
3
+ Creative.where("origin_id = id").update_all(origin_id: nil)
4
+ end
5
+
6
+ def down
7
+ # Irreversible as we cannot know which ones were self-referencing if valid data also had nil
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ class CreateTopicsAndAddTopicToComments < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :topics do |t|
4
+ t.references :creative, null: false, foreign_key: true
5
+ t.references :user, null: false, foreign_key: true
6
+ t.string :name, null: false
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :topics, [ :creative_id, :name ], unique: true
12
+
13
+ add_reference :comments, :topic, foreign_key: true, null: true
14
+ end
15
+ end