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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateActivityLogs < ActiveRecord::Migration[7.1]
4
+ def change
5
+ create_table :activity_logs do |t|
6
+ t.string :activity, null: false
7
+ t.json :log, default: {}, null: false
8
+ t.references :creative, foreign_key: true, null: true
9
+ t.references :user, foreign_key: true, null: true
10
+ t.references :comment, foreign_key: true, null: true
11
+ t.datetime :created_at, null: false
12
+ end
13
+
14
+ add_index :activity_logs, :created_at
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ class CreateWebauthnCredentials < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :webauthn_credentials do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :webauthn_id, null: false
6
+ t.string :public_key, null: false
7
+ t.integer :sign_count, default: 0, null: false
8
+ t.string :nickname
9
+
10
+ t.timestamps
11
+ end
12
+ add_index :webauthn_credentials, :webauthn_id, unique: true
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class AddWebauthnIdToUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :users, :webauthn_id, :string
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCommentReactions < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :comment_reactions do |t|
4
+ t.references :comment, null: false, foreign_key: true
5
+ t.references :user, null: false, foreign_key: true
6
+ t.string :emoji, null: false
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :comment_reactions, [ :comment_id, :user_id, :emoji ], unique: true
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUserThemes < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :user_themes do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :name
6
+ t.json :variables
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class AddCreativeIdToLabels < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_reference :labels, :creative, foreign_key: true
4
+ end
5
+ end
@@ -0,0 +1,38 @@
1
+ class RefactorLabels < ActiveRecord::Migration[8.1]
2
+ def up
3
+ puts "Start refactoring labels"
4
+ # Data migration: Create Creatives for Labels that don't have one
5
+ Label.where(creative_id: nil).find_each do |label|
6
+ # Use label[:name] to bypass alias_method :name, :description in Label model
7
+ # which returns nil when creative_id is nil
8
+ actual_name = label[:name]
9
+ puts "Processing label #{actual_name}"
10
+ next if actual_name.blank?
11
+
12
+ # Create a new Creative using the Label's name and owner
13
+ creative = Creative.create!(
14
+ description: actual_name,
15
+ user_id: label.owner_id,
16
+ progress: 0.0 # Default value
17
+ )
18
+
19
+ puts "Update label #{label.id} with creative #{creative.id}"
20
+ label.update!(creative_id: creative.id)
21
+ end
22
+
23
+ # Schema changes
24
+ change_column_null :labels, :creative_id, false
25
+ remove_column :labels, :name
26
+ end
27
+
28
+ def down
29
+ add_column :labels, :name, :string
30
+ change_column_null :labels, :creative_id, true
31
+
32
+ # Optional: We could attempt to restore names from creatives, but
33
+ # since we can't be sure which creative was created specifically for the label migration vs manual,
34
+ # strictly speaking the down migration might just restore schema.
35
+ # But for completeness let's try to put creative description back to name if it matches?
36
+ # For now, just restoring schema is safer.
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ class BackfillTagsForLabels < ActiveRecord::Migration[8.1]
2
+ def up
3
+ Label.where.not(creative_id: nil).find_each do |label|
4
+ next if Tag.exists?(label_id: label.id, creative_id: label.creative_id)
5
+
6
+ Tag.create!(label_id: label.id, creative_id: label.creative_id)
7
+ end
8
+ end
9
+
10
+ def down
11
+ # No-op or potentially delete tags created by this migration
12
+ # Since tags might have been created manually later, it's safer to avoid bulk deletion
13
+ # unless we track which ones were created here.
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class DropSubscribers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ drop_table :subscribers do |t|
4
+ t.belongs_to :creative, null: false, foreign_key: true
5
+ t.string :email
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class AllowNullUserIdInCreativeShares < ActiveRecord::Migration[8.1]
2
+ def change
3
+ change_column_null :creative_shares, :user_id, true
4
+ add_index :creative_shares, :creative_id, unique: true, where: "user_id IS NULL", name: "index_creative_shares_on_creative_id_public"
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ class CreateSystemSettings < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :system_settings do |t|
4
+ t.string :key
5
+ t.text :value
6
+
7
+ t.timestamps
8
+ end
9
+ add_index :system_settings, :key, unique: true
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ class CreateCreativeSharesCache < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :creative_shares_caches do |t|
4
+ t.references :creative, null: false, foreign_key: true
5
+ t.references :user, null: true, foreign_key: true
6
+ t.integer :permission, null: false
7
+ t.references :source_share, null: false, foreign_key: { to_table: :creative_shares }
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :creative_shares_caches, [ :creative_id, :user_id ], unique: true
12
+ add_index :creative_shares_caches, [ :user_id, :permission ]
13
+ # source_share_id index is already created by t.references
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ class PopulateCreativeSharesCache < ActiveRecord::Migration[8.1]
2
+ def up
3
+ say_with_time "Populating creative_shares_cache" do
4
+ # Ensure the service is available before running
5
+ unless defined?(Creatives::PermissionCacheBuilder)
6
+ Rails.logger.warn "PermissionCacheBuilder not available, skipping cache population"
7
+ return 0
8
+ end
9
+
10
+ count = 0
11
+ # Include all shares including no_access (needed to override public shares)
12
+ CreativeShare.find_each do |share|
13
+ Creatives::PermissionCacheBuilder.propagate_share(share)
14
+ count += 1
15
+ end
16
+ count
17
+ end
18
+ end
19
+
20
+ def down
21
+ execute "DELETE FROM creative_shares_caches"
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ class MakeSourceShareIdNullableInCreativeSharesCaches < ActiveRecord::Migration[8.1]
2
+ def change
3
+ change_column_null :creative_shares_caches, :source_share_id, true
4
+ remove_foreign_key :creative_shares_caches, :creative_shares
5
+ add_foreign_key :creative_shares_caches, :creative_shares, column: :source_share_id, on_delete: :cascade, validate: false
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ class PopulateOwnerCacheEntries < ActiveRecord::Migration[8.1]
2
+ def up
3
+ say_with_time "Populating owner cache entries" do
4
+ # Insert owner entries for all creatives (admin = 4)
5
+ # Use INSERT ... ON CONFLICT to avoid duplicates
6
+ # datetime('now') for SQLite compatibility
7
+ execute <<~SQL
8
+ INSERT INTO creative_shares_caches (creative_id, user_id, permission, source_share_id, created_at, updated_at)
9
+ SELECT id, user_id, 4, NULL, datetime('now'), datetime('now')
10
+ FROM creatives
11
+ WHERE user_id IS NOT NULL
12
+ ON CONFLICT (creative_id, user_id) DO NOTHING
13
+ SQL
14
+ end
15
+ end
16
+
17
+ def down
18
+ say_with_time "Removing owner cache entries" do
19
+ execute <<~SQL
20
+ DELETE FROM creative_shares_caches
21
+ WHERE source_share_id IS NULL
22
+ SQL
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ class AddAccountLockoutToUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :users, :failed_login_attempts, :integer, default: 0, null: false
4
+ add_column :users, :locked_at, :datetime
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class AddLastActiveAtToSessions < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :sessions, :last_active_at, :datetime
4
+ add_index :sessions, :last_active_at
5
+ end
6
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EncryptOauthTokens < ActiveRecord::Migration[8.1]
4
+ def up
5
+ say_with_time "Encrypting User Google tokens" do
6
+ encrypt_column(User, :google_access_token)
7
+ encrypt_column(User, :google_refresh_token)
8
+ end
9
+
10
+ say_with_time "Encrypting GithubAccount tokens" do
11
+ encrypt_column(GithubAccount, :token)
12
+ end
13
+
14
+ say_with_time "Encrypting NotionAccount tokens" do
15
+ encrypt_column(NotionAccount, :token)
16
+ end
17
+ end
18
+
19
+ def down
20
+ # No-op: Decryption happens automatically when reading encrypted values
21
+ # The `encrypts` declaration in the model handles transparent decryption
22
+ end
23
+
24
+ private
25
+
26
+ def encrypt_column(model, attribute)
27
+ table_name = model.table_name
28
+
29
+ # Read plaintext values directly from database using raw SQL
30
+ records_to_encrypt = ActiveRecord::Base.connection.select_all(
31
+ "SELECT id, #{attribute} FROM #{table_name} WHERE #{attribute} IS NOT NULL"
32
+ ).to_a
33
+
34
+ records_to_encrypt.each do |row|
35
+ record_id = row["id"]
36
+ plaintext_value = row[attribute.to_s]
37
+
38
+ # Skip if already encrypted (value looks like JSON with encryption markers)
39
+ next if plaintext_value.nil? || encrypted?(plaintext_value)
40
+
41
+ # Encrypt the plaintext value using the model's encryptor
42
+ encryptor = model.type_for_attribute(attribute)
43
+ encrypted_value = encryptor.serialize(plaintext_value)
44
+
45
+ # Write encrypted value directly to database
46
+ ActiveRecord::Base.connection.execute(
47
+ "UPDATE #{table_name} SET #{attribute} = #{ActiveRecord::Base.connection.quote(encrypted_value)} WHERE id = #{record_id}"
48
+ )
49
+ end
50
+ end
51
+
52
+ def encrypted?(value)
53
+ # Active Record Encryption uses JSON format starting with specific markers
54
+ return false unless value.is_a?(String)
55
+
56
+ value.start_with?("{") && value.include?('"p":')
57
+ rescue StandardError
58
+ false
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ class RemoveFkFromCreativeSharesCaches < ActiveRecord::Migration[8.1]
2
+ def change
3
+ remove_foreign_key :creative_shares_caches, :creatives, if_exists: true
4
+ remove_foreign_key :creative_shares_caches, :users, if_exists: true
5
+ remove_foreign_key :creative_shares_caches, column: :source_share_id, if_exists: true
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class RemoveTimestampsFromCreativeSharesCaches < ActiveRecord::Migration[8.1]
2
+ def change
3
+ remove_column :creative_shares_caches, :created_at, :datetime
4
+ remove_column :creative_shares_caches, :updated_at, :datetime
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ module Collavre
2
+ class Configuration
3
+ attr_accessor :user_class_name, :current_user_method
4
+
5
+ def initialize
6
+ @user_class_name = "User"
7
+ @current_user_method = -> { Current.user }
8
+ end
9
+
10
+ def user_class
11
+ @user_class_name.constantize
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ module Collavre
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Collavre
4
+
5
+ config.generators do |g|
6
+ g.test_framework :minitest
7
+ end
8
+
9
+ # Path to engine's JavaScript sources for jsbundling-rails integration
10
+ def self.javascript_path
11
+ root.join("app/javascript")
12
+ end
13
+
14
+ # Path to engine's stylesheet sources
15
+ def self.stylesheet_path
16
+ root.join("app/assets/stylesheets")
17
+ end
18
+
19
+ # Add engine migrations to main app's migration path
20
+ # This allows migrations to live in the engine but be run from the host app
21
+ initializer "collavre.migrations" do |app|
22
+ unless app.root.to_s.match?(root.to_s)
23
+ config.paths["db/migrate"].expanded.each do |expanded_path|
24
+ app.config.paths["db/migrate"] << expanded_path
25
+ end
26
+ end
27
+ end
28
+
29
+ initializer "collavre.assets" do |app|
30
+ app.config.assets.precompile += %w[collavre.js] if app.config.respond_to?(:assets)
31
+
32
+ # Add engine stylesheets to asset paths for Propshaft
33
+ if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:paths)
34
+ app.config.assets.paths << root.join("app/assets/stylesheets")
35
+ end
36
+ end
37
+
38
+ initializer "collavre.importmap", before: "importmap" do |app|
39
+ if app.config.respond_to?(:importmap)
40
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
41
+ end
42
+ end
43
+
44
+ # Add engine locales to I18n load path
45
+ # Host app can override these translations by defining the same keys
46
+ initializer "collavre.i18n" do |app|
47
+ config.i18n.load_path += Dir[root.join("config/locales/**/*.yml")]
48
+ end
49
+
50
+ # Allow engine controllers to fall back to host app views during migration
51
+ # This enables gradual view migration - views can stay in host app until moved to engine
52
+ initializer "collavre.view_paths" do
53
+ ActiveSupport.on_load(:action_controller) do
54
+ append_view_path Rails.root.join("app/views")
55
+ end
56
+ end
57
+
58
+ # Make engine URL helpers available to controllers and views via the `collavre` method
59
+ # This avoids conflicts with main app routes while still providing access to engine routes
60
+ initializer "collavre.url_helpers" do
61
+ ActiveSupport.on_load(:action_controller_base) do
62
+ # Add to controllers
63
+ define_method :collavre do
64
+ Collavre::Engine.routes.url_helpers
65
+ end
66
+ private :collavre
67
+
68
+ # Add to views via helper
69
+ helper do
70
+ def collavre
71
+ Collavre::Engine.routes.url_helpers
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ module Collavre
2
+ module UserExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ # Core Collavre associations
7
+ has_many :collavre_creatives, class_name: "Collavre::Creative", dependent: :destroy
8
+ has_many :collavre_comments, class_name: "Collavre::Comment", dependent: :destroy
9
+ has_many :collavre_creative_shares, class_name: "Collavre::CreativeShare", dependent: :destroy
10
+ has_many :collavre_shared_creative_shares, class_name: "Collavre::CreativeShare",
11
+ foreign_key: :shared_by_id, dependent: :nullify, inverse_of: :shared_by
12
+ has_many :collavre_topics, class_name: "Collavre::Topic", dependent: :destroy
13
+ has_many :collavre_inbox_items, class_name: "Collavre::InboxItem",
14
+ foreign_key: :owner_id, dependent: :destroy, inverse_of: :owner
15
+ has_many :collavre_invitations, class_name: "Collavre::Invitation",
16
+ foreign_key: :inviter_id, dependent: :destroy, inverse_of: :inviter
17
+ has_many :collavre_contacts, class_name: "Collavre::Contact", dependent: :destroy
18
+ has_many :collavre_contact_memberships, class_name: "Collavre::Contact",
19
+ foreign_key: :contact_user_id, dependent: :destroy, inverse_of: :contact_user
20
+ has_many :collavre_user_themes, class_name: "Collavre::UserTheme", dependent: :destroy
21
+ has_many :collavre_devices, class_name: "Collavre::Device", dependent: :destroy
22
+ has_many :collavre_calendar_events, class_name: "Collavre::CalendarEvent", dependent: :destroy
23
+ has_many :collavre_labels, class_name: "Collavre::Label", foreign_key: :owner_id, dependent: :destroy
24
+ has_many :collavre_activity_logs, class_name: "Collavre::ActivityLog", dependent: :destroy
25
+ has_many :collavre_comment_read_pointers, class_name: "Collavre::CommentReadPointer", dependent: :destroy
26
+ has_many :collavre_creative_expanded_states, class_name: "Collavre::CreativeExpandedState", dependent: :destroy
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module Collavre
2
+ VERSION = "0.1.0"
3
+ end
data/lib/collavre.rb ADDED
@@ -0,0 +1,26 @@
1
+ require "collavre/version"
2
+ require "collavre/configuration"
3
+ require "collavre/engine"
4
+ require "collavre/user_extensions"
5
+
6
+ module Collavre
7
+ class << self
8
+ attr_writer :configuration
9
+
10
+ def configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def configure
15
+ yield(configuration)
16
+ end
17
+
18
+ def user_class
19
+ configuration.user_class
20
+ end
21
+
22
+ def current_user
23
+ configuration.current_user_method.call
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Collavre
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Install Collavre engine assets for jsbundling-rails"
9
+
10
+ class_option :replace_build_script, type: :boolean, default: false,
11
+ desc: "Replace script/build.cjs entirely (recommended for new projects)"
12
+
13
+ def install_build_script
14
+ build_script = Rails.root.join("script/build.cjs")
15
+
16
+ if options[:replace_build_script] || !File.exist?(build_script)
17
+ template "build.cjs.tt", "script/build.cjs"
18
+ say_status :create, "script/build.cjs (with Collavre support)", :green
19
+ else
20
+ update_existing_build_script(build_script)
21
+ end
22
+ end
23
+
24
+ def show_post_install
25
+ say ""
26
+ say "Collavre assets installed!", :green
27
+ say ""
28
+ say "The build script now automatically finds Collavre gem and includes its assets."
29
+ say ""
30
+ say "Stylesheets are automatically available via Propshaft."
31
+ say "Add to your application.css if needed:"
32
+ say " @import 'collavre/creatives';"
33
+ say " @import 'collavre/comments_popup';"
34
+ say " @import 'collavre/dark_mode';"
35
+ say ""
36
+ say "Run 'npm run build' to build assets."
37
+ say ""
38
+ end
39
+
40
+ private
41
+
42
+ def update_existing_build_script(build_script)
43
+ content = File.read(build_script)
44
+
45
+ # Check if already installed
46
+ if content.include?("collavre") || content.include?("COLLAVRE_GEM_PATH")
47
+ say_status :skip, "Collavre already configured in build script", :yellow
48
+ return
49
+ end
50
+
51
+ # Add gem path detection function after requires
52
+ gem_path_function = <<~JS
53
+
54
+ // Find Collavre gem path using bundler
55
+ const { execSync } = require('child_process');
56
+ function getCollavreGemPath() {
57
+ if (process.env.COLLAVRE_GEM_PATH) return process.env.COLLAVRE_GEM_PATH;
58
+ try {
59
+ const gemPath = execSync('bundle show collavre 2>/dev/null', { encoding: 'utf8' }).trim();
60
+ if (gemPath && !gemPath.includes('Could not find')) return gemPath;
61
+ } catch (e) {}
62
+ const localPath = path.join(process.cwd(), 'engines/collavre');
63
+ if (require('fs').existsSync(localPath)) return localPath;
64
+ return null;
65
+ }
66
+ JS
67
+
68
+ # Add gem entry point discovery
69
+ gem_discovery_code = <<~JS
70
+
71
+ // Add Collavre gem entry points
72
+ const collavreGemPath = getCollavreGemPath();
73
+ if (collavreGemPath) {
74
+ console.log(`[INFO] Including Collavre assets from: ${collavreGemPath}`);
75
+ const collavreEntries = glob.sync(path.join(collavreGemPath, 'app/javascript/*.*'));
76
+ collavreEntries.forEach(entry => {
77
+ const name = path.parse(entry).name;
78
+ if (!entryPoints[name]) entryPoints[name] = entry;
79
+ });
80
+ }
81
+ JS
82
+
83
+ # Insert function after requires
84
+ if content.include?("const path = require('path');")
85
+ content = content.sub(
86
+ "const path = require('path');",
87
+ "const path = require('path');#{gem_path_function}"
88
+ )
89
+ end
90
+
91
+ # Insert discovery before the empty check
92
+ if content.include?("if (Object.keys(entryPoints).length === 0)")
93
+ content = content.sub(
94
+ "if (Object.keys(entryPoints).length === 0)",
95
+ "#{gem_discovery_code}\nif (Object.keys(entryPoints).length === 0)"
96
+ )
97
+ File.write(build_script, content)
98
+ say_status :update, "script/build.cjs", :green
99
+ else
100
+ say_status :error, "Could not find insertion point in build script. Use --replace-build-script option.", :red
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end