collavre 0.1.1 → 0.2.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/collavre/comments_popup.css +293 -8
  3. data/app/assets/stylesheets/collavre/mention_menu.css +26 -0
  4. data/app/assets/stylesheets/collavre/popup.css +7 -0
  5. data/app/assets/stylesheets/collavre/print.css +18 -0
  6. data/app/channels/collavre/comments_presence_channel.rb +33 -0
  7. data/app/components/collavre/autocomplete_popup_component.html.erb +3 -0
  8. data/app/components/collavre/autocomplete_popup_component.rb +18 -0
  9. data/app/components/collavre/command_menu_component.rb +7 -0
  10. data/app/components/collavre/plans_timeline_component.html.erb +1 -1
  11. data/app/components/collavre/plans_timeline_component.rb +29 -32
  12. data/app/components/collavre/user_mention_menu_component.rb +4 -5
  13. data/app/controllers/collavre/comments_controller.rb +111 -10
  14. data/app/controllers/collavre/creatives_controller.rb +8 -0
  15. data/app/controllers/collavre/google_auth_controller.rb +5 -1
  16. data/app/controllers/collavre/plans_controller.rb +65 -9
  17. data/app/controllers/collavre/topics_controller.rb +42 -0
  18. data/app/controllers/collavre/users_controller.rb +4 -14
  19. data/app/errors/collavre/approval_pending_error.rb +54 -0
  20. data/app/errors/collavre/cancelled_error.rb +9 -0
  21. data/app/helpers/collavre/navigation_helper.rb +3 -1
  22. data/app/javascript/collavre.js +1 -0
  23. data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +2 -1
  24. data/app/javascript/controllers/comments/form_controller.js +2 -1
  25. data/app/javascript/controllers/comments/list_controller.js +185 -2
  26. data/app/javascript/controllers/comments/popup_controller.js +95 -20
  27. data/app/javascript/controllers/comments/presence_controller.js +30 -1
  28. data/app/javascript/controllers/comments/topics_controller.js +314 -4
  29. data/app/javascript/modules/__tests__/creative_progress.test.js +50 -0
  30. data/app/javascript/modules/command_menu.js +116 -0
  31. data/app/javascript/modules/creative_progress.js +14 -0
  32. data/app/javascript/modules/creative_row_editor.js +104 -20
  33. data/app/javascript/modules/plans_timeline.js +15 -4
  34. data/app/javascript/modules/share_modal.js +3 -0
  35. data/app/jobs/collavre/ai_agent_job.rb +35 -21
  36. data/app/models/collavre/calendar_event.rb +7 -1
  37. data/app/models/collavre/comment.rb +35 -2
  38. data/app/models/collavre/creative.rb +1 -3
  39. data/app/models/collavre/mcp_tool.rb +4 -0
  40. data/app/models/collavre/plan.rb +23 -0
  41. data/app/models/collavre/topic.rb +12 -0
  42. data/app/models/collavre/user.rb +15 -1
  43. data/app/services/collavre/ai_agent_service.rb +174 -66
  44. data/app/services/collavre/ai_client.rb +31 -2
  45. data/app/services/collavre/comments/action_executor.rb +47 -1
  46. data/app/services/collavre/comments/calendar_command.rb +117 -18
  47. data/app/services/collavre/google_calendar_service.rb +38 -15
  48. data/app/services/collavre/markdown_importer.rb +47 -8
  49. data/app/services/collavre/mcp_service.rb +23 -10
  50. data/app/services/collavre/system_events/router.rb +50 -26
  51. data/app/services/collavre/tools/creative_create_service.rb +97 -0
  52. data/app/services/collavre/tools/creative_update_service.rb +116 -0
  53. data/app/views/collavre/comments/_comment.html.erb +2 -2
  54. data/app/views/collavre/comments/_comments_popup.html.erb +40 -6
  55. data/app/views/collavre/comments/fullscreen.html.erb +5 -0
  56. data/app/views/collavre/creatives/_inline_edit_form.html.erb +11 -3
  57. data/app/views/collavre/creatives/_integration_modals.html.erb +6 -0
  58. data/app/views/collavre/creatives/_integration_triggers.html.erb +8 -0
  59. data/app/views/collavre/creatives/_integrations_menu.html.erb +12 -0
  60. data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +13 -1
  61. data/app/views/collavre/creatives/_share_button.html.erb +1 -1
  62. data/app/views/collavre/creatives/index.html.erb +22 -4
  63. data/app/views/collavre/users/edit_ai.html.erb +15 -0
  64. data/app/views/collavre/users/new_ai.html.erb +15 -0
  65. data/app/views/layouts/collavre/chat.html.erb +46 -0
  66. data/config/locales/ai_agent.en.yml +15 -0
  67. data/config/locales/ai_agent.ko.yml +15 -0
  68. data/config/locales/comments.en.yml +15 -3
  69. data/config/locales/comments.ko.yml +15 -3
  70. data/config/locales/creatives.en.yml +3 -31
  71. data/config/locales/creatives.ko.yml +3 -27
  72. data/config/locales/plans.en.yml +4 -0
  73. data/config/locales/plans.ko.yml +4 -0
  74. data/config/locales/users.en.yml +3 -0
  75. data/config/locales/users.ko.yml +3 -0
  76. data/config/routes.rb +8 -3
  77. data/db/migrate/20260120045354_encrypt_oauth_tokens.rb +1 -1
  78. data/db/migrate/20260131100000_migrate_active_storage_attachment_record_types.rb +21 -0
  79. data/db/migrate/20260201100000_make_google_event_id_nullable.rb +5 -0
  80. data/lib/collavre/engine.rb +171 -6
  81. data/lib/collavre/integration_registry.rb +129 -0
  82. data/lib/collavre/version.rb +1 -1
  83. data/lib/collavre.rb +2 -0
  84. data/lib/navigation/registry.rb +130 -0
  85. metadata +22 -15
  86. data/app/components/collavre/user_mention_menu_component.html.erb +0 -3
  87. data/app/controllers/collavre/notion_auth_controller.rb +0 -25
  88. data/app/jobs/collavre/notion_export_job.rb +0 -30
  89. data/app/jobs/collavre/notion_sync_job.rb +0 -48
  90. data/app/models/collavre/notion_account.rb +0 -17
  91. data/app/models/collavre/notion_block_link.rb +0 -10
  92. data/app/models/collavre/notion_page_link.rb +0 -19
  93. data/app/services/collavre/notion_client.rb +0 -231
  94. data/app/services/collavre/notion_creative_exporter.rb +0 -296
  95. data/app/services/collavre/notion_service.rb +0 -249
  96. data/app/views/collavre/creatives/_notion_integration_modal.html.erb +0 -90
  97. data/db/migrate/20241201000000_create_notion_integrations.rb +0 -29
  98. data/db/migrate/20250312000000_create_notion_block_links.rb +0 -16
  99. data/db/migrate/20250312010000_allow_multiple_notion_blocks_per_creative.rb +0 -5
@@ -1,249 +0,0 @@
1
- module Collavre
2
- require "digest"
3
-
4
- class NotionService
5
- def initialize(user:)
6
- @user = user
7
- @account = user.notion_account
8
- raise NotionAuthError, "No Notion account found" unless @account
9
- end
10
-
11
- def client
12
- @client ||= NotionClient.new(@account)
13
- end
14
-
15
- def search_pages(query: nil, start_cursor: nil, page_size: 10)
16
- with_token_refresh { client.search_pages(query: query, start_cursor: start_cursor, page_size: page_size) }
17
- end
18
-
19
- def get_page(page_id)
20
- with_token_refresh { client.get_page(page_id) }
21
- end
22
-
23
- def create_page(parent_id:, title:, blocks: [])
24
- with_token_refresh { client.create_page(parent_id: parent_id, title: title, blocks: blocks) }
25
- end
26
-
27
- def update_page(page_id, properties: {}, blocks: nil)
28
- with_token_refresh { client.update_page(page_id, properties: properties, blocks: blocks) }
29
- end
30
-
31
- def get_page_blocks(page_id, start_cursor: nil, page_size: 100)
32
- with_token_refresh { client.get_page_blocks(page_id, start_cursor: start_cursor, page_size: page_size) }
33
- end
34
-
35
- def replace_page_blocks(page_id, blocks)
36
- with_token_refresh { client.replace_page_blocks(page_id, blocks) }
37
- end
38
-
39
- def append_blocks(parent_id, blocks)
40
- with_token_refresh { client.append_blocks(parent_id, blocks) }
41
- end
42
-
43
- def delete_block(block_id)
44
- with_token_refresh { client.delete_block(block_id) }
45
- end
46
-
47
- def get_workspace
48
- with_token_refresh { client.get_workspace }
49
- end
50
-
51
- # Create or update a page for a creative
52
- def sync_creative(creative, parent_page_id: nil)
53
- notion_link = find_or_create_page_link(creative, parent_page_id)
54
-
55
- if notion_link.page_id.present?
56
- # Update existing page
57
- update_creative_page(creative, notion_link)
58
- else
59
- # Create new page
60
- create_creative_page(creative, notion_link, parent_page_id)
61
- end
62
-
63
- notion_link.mark_synced!
64
- notion_link
65
- end
66
-
67
- private
68
-
69
- def with_token_refresh(&block)
70
- yield
71
- rescue NotionAuthError => e
72
- if refresh_token!
73
- @client = nil # Reset client with new token
74
- yield
75
- else
76
- raise e
77
- end
78
- end
79
-
80
- def refresh_token!
81
- # Notion uses OAuth 2.0 but doesn't issue refresh tokens in the same way as Google
82
- # For now, we'll just log the error and return false
83
- # In a production app, you'd implement proper token refresh logic here
84
- Rails.logger.error("Notion token refresh needed but not implemented")
85
- false
86
- end
87
-
88
- def find_or_create_page_link(creative, parent_page_id)
89
- @account.notion_page_links.find_or_initialize_by(creative: creative) do |link|
90
- link.parent_page_id = parent_page_id
91
- end
92
- end
93
-
94
- def create_creative_page(creative, notion_link, parent_page_id)
95
- title = ActionController::Base.helpers.strip_tags(creative.description).strip.presence || "Untitled Creative"
96
-
97
- # Export only the children - the page title serves as the root creative
98
- children = creative.children.to_a
99
- Rails.logger.info("NotionService: Exporting creative #{creative.id} as page title with #{children.count} children as blocks")
100
-
101
- exporter = NotionCreativeExporter.new(creative)
102
- blocks = []
103
-
104
- # If no parent specified, search for a suitable workspace page
105
- parent_page_id ||= find_default_parent_page
106
-
107
- response = create_page(
108
- parent_id: parent_page_id,
109
- title: title,
110
- blocks: blocks
111
- )
112
-
113
- notion_link.update!(
114
- page_id: response["id"],
115
- page_title: title,
116
- page_url: response["url"],
117
- parent_page_id: parent_page_id
118
- )
119
-
120
- sync_child_blocks(notion_link, creative, children, exporter)
121
-
122
- response
123
- end
124
-
125
- def update_creative_page(creative, notion_link)
126
- title = ActionController::Base.helpers.strip_tags(creative.description).strip.presence || "Untitled Creative"
127
-
128
- # Update with only the children - page title serves as the root creative
129
- children = creative.children.to_a
130
- Rails.logger.info("NotionService: Updating creative #{creative.id} as page title with #{children.count} children as blocks")
131
-
132
- exporter = NotionCreativeExporter.new(creative)
133
-
134
- properties = {
135
- title: {
136
- title: [ { text: { content: title } } ]
137
- }
138
- }
139
-
140
- update_page(notion_link.page_id, properties: properties)
141
- notion_link.update!(page_title: title)
142
-
143
- sync_child_blocks(notion_link, creative, children, exporter)
144
- end
145
-
146
- def find_default_parent_page
147
- # Search for pages in the workspace to find a suitable parent
148
- pages = search_pages(page_size: 1)
149
- pages.dig("results")&.first&.dig("id") || raise(NotionError, "No accessible pages found in workspace")
150
- end
151
-
152
- def sync_child_blocks(notion_link, creative, children, exporter)
153
- child_ids = children.map(&:id)
154
- existing_links = notion_link.notion_block_links.includes(:creative).order(:created_at).to_a
155
- existing_links_by_creative = existing_links.group_by(&:creative_id)
156
-
157
- page_blocks = existing_links.any? ? fetch_all_page_blocks(notion_link.page_id) : []
158
- page_block_ids = page_blocks.map { |block| block["id"] }
159
-
160
- block_to_creative = {}
161
- existing_links.each do |link|
162
- block_to_creative[link.block_id] = link.creative_id if page_block_ids.include?(link.block_id)
163
- end
164
-
165
- existing_order = page_block_ids.map { |block_id| block_to_creative[block_id] }.compact.uniq
166
- expected_order = child_ids.select { |id| existing_links_by_creative.key?(id) }
167
- reorder_detected = existing_order != expected_order
168
-
169
- removed_ids = existing_links_by_creative.keys - child_ids
170
- changes_detected = removed_ids.any?
171
-
172
- child_exports = children.map do |child|
173
- exported_blocks = exporter.export_tree_blocks([ child ], 1, 0)
174
- content_hash = Digest::SHA256.hexdigest(exported_blocks.to_json)
175
- links = existing_links_by_creative[child.id] || []
176
- missing_blocks = links.any? { |link| !page_block_ids.include?(link.block_id) }
177
-
178
- if exported_blocks.empty?
179
- changes_detected ||= links.present?
180
- else
181
- changes_detected ||= links.blank?
182
- changes_detected ||= links.size != exported_blocks.size
183
- changes_detected ||= links.first.content_hash != content_hash
184
- changes_detected ||= missing_blocks
185
- end
186
-
187
- {
188
- child: child,
189
- exported_blocks: exported_blocks,
190
- content_hash: content_hash
191
- }
192
- end
193
-
194
- unless changes_detected || reorder_detected
195
- return
196
- end
197
-
198
- blocks_to_clear = page_blocks.presence || fetch_all_page_blocks(notion_link.page_id)
199
- blocks_to_clear.each do |block|
200
- begin
201
- delete_block(block["id"])
202
- rescue NotionError => e
203
- Rails.logger.warn("NotionService: Failed to delete Notion block #{block['id']} during resync: #{e.message}")
204
- end
205
- end
206
-
207
- NotionBlockLink.transaction do
208
- notion_link.notion_block_links.delete_all
209
-
210
- child_exports.each do |data|
211
- exported_blocks = data[:exported_blocks]
212
- next if exported_blocks.empty?
213
-
214
- response = append_blocks(notion_link.page_id, exported_blocks)
215
- response_results = response.is_a?(Hash) ? response.fetch("results", []) : []
216
- new_block_ids = response_results.filter_map { |result| result["id"] }
217
-
218
- if new_block_ids.empty?
219
- Rails.logger.warn("NotionService: Unable to determine new block ids for creative #{data[:child].id}")
220
- next
221
- end
222
-
223
- new_block_ids.each do |block_id|
224
- notion_link.notion_block_links.create!(
225
- creative: data[:child],
226
- block_id: block_id,
227
- content_hash: data[:content_hash]
228
- )
229
- end
230
- end
231
- end
232
- end
233
-
234
- def fetch_all_page_blocks(page_id)
235
- blocks = []
236
- cursor = nil
237
-
238
- loop do
239
- response = get_page_blocks(page_id, start_cursor: cursor)
240
- blocks.concat(response.fetch("results", []))
241
- break unless response["has_more"]
242
-
243
- cursor = response["next_cursor"]
244
- end
245
-
246
- blocks
247
- end
248
- end
249
- end
@@ -1,90 +0,0 @@
1
- <div id="notion-integration-modal"
2
- data-success-message="<%= t('collavre.creatives.index.notion_integration_saved', default: 'Notion integration saved successfully.') %>"
3
- data-login-required="<%= t('collavre.creatives.index.notion_integration_login_required', default: 'Sign in with your Notion account to start the integration.') %>"
4
- data-no-creative="<%= t('collavre.creatives.index.notion_integration_missing_creative', default: 'No Creative selected for integration.') %>"
5
- data-existing-message="<%= t('collavre.creatives.index.notion_integration_existing_message', default: 'You\'re already connected to Notion pages below.') %>"
6
- data-delete-confirm="<%= t('collavre.creatives.index.notion_integration_delete_confirm', default: 'Do you want to remove the Notion integration?') %>"
7
- data-delete-success="<%= t('collavre.creatives.index.notion_integration_delete_success', default: 'Notion integration removed successfully.') %>"
8
- data-delete-error="<%= t('collavre.creatives.index.notion_integration_delete_error', default: 'Failed to remove the Notion integration.') %>"
9
- data-delete-button-label="<%= t('collavre.creatives.index.notion_integration_delete_button', default: 'Remove integration') %>"
10
- data-export-success="<%= t('collavre.creatives.index.notion_export_success', default: 'Creative exported to Notion successfully.') %>"
11
- data-sync-success="<%= t('collavre.creatives.index.notion_sync_success', default: 'Creative synced to Notion successfully.') %>"
12
- style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;align-items:center;justify-content:center;">
13
- <div class="popup-box" style="min-width:360px;max-width:90vw;">
14
- <button type="button" id="close-notion-modal" class="popup-close-btn">&times;</button>
15
- <h2><%= t('collavre.creatives.index.notion_integration_title', default: 'Configure Notion integration') %></h2>
16
- <p id="notion-integration-status" class="notion-modal-status"></p>
17
-
18
- <div class="notion-wizard-step" id="notion-step-connect">
19
- <p id="notion-connect-message" class="notion-modal-subtext"><%= t('collavre.creatives.index.notion_integration_connect', default: 'Sign in with your Notion account to start exporting.') %></p>
20
- <form id="notion-login-form" action="/auth/notion" method="get" target="notion-auth-window" style="display:none;">
21
- </form>
22
- <button type="button" id="notion-login-btn" class="btn btn-primary" data-window-width="620" data-window-height="720">
23
- <%= t('collavre.creatives.index.notion_login_button', default: 'Sign in with Notion') %>
24
- </button>
25
- <div id="notion-existing-connections" style="display:none;margin-top:1.25em;">
26
- <p style="margin-bottom:0.5em;"><%= t('collavre.creatives.index.notion_integration_existing_intro', default: 'Linked Notion pages:') %></p>
27
- <ul id="notion-existing-page-list" style="padding-left:1.2em;margin-bottom:0.75em;color:var(--color-text);"></ul>
28
- <div style="display:flex;gap:0.5em;">
29
- <button type="button" id="notion-sync-btn" class="btn btn-secondary" style="display:none;">
30
- <%= t('collavre.creatives.index.notion_sync_button', default: 'Sync to Notion') %>
31
- </button>
32
- <button type="button" id="notion-delete-btn" class="btn btn-danger" style="display:none;">
33
- <%= t('collavre.creatives.index.notion_integration_delete_button', default: 'Remove link') %>
34
- </button>
35
- </div>
36
- </div>
37
- </div>
38
-
39
- <div class="notion-wizard-step" id="notion-step-workspace" style="display:none;">
40
- <p class="notion-modal-subtext"><%= t('collavre.creatives.index.notion_integration_workspace', default: 'Your Notion workspace is connected. Choose how to export your creative.') %></p>
41
- <div id="notion-workspace-info" style="margin-bottom:1em;padding:1em;background:var(--color-bg-alt);border-radius:4px;">
42
- <strong id="notion-workspace-name"></strong>
43
- </div>
44
- <div style="margin-bottom:1em;">
45
- <label style="display:block;margin-bottom:0.5em;">
46
- <%= t('collavre.creatives.index.notion_export_option', default: 'Export option:') %>
47
- </label>
48
- <div style="display:flex;flex-direction:column;gap:0.5em;">
49
- <label style="display:flex;align-items:center;gap:0.5em;">
50
- <input type="radio" name="notion-export-type" value="new-page" checked>
51
- <%= t('collavre.creatives.index.notion_export_new_page', default: 'Create new page') %>
52
- </label>
53
- <label style="display:flex;align-items:center;gap:0.5em;">
54
- <input type="radio" name="notion-export-type" value="select-parent">
55
- <%= t('collavre.creatives.index.notion_export_under_page', default: 'Create as subpage under existing page') %>
56
- </label>
57
- </div>
58
- </div>
59
- <div id="notion-parent-page-section" style="display:none;margin-bottom:1em;">
60
- <label for="notion-parent-page-select" style="display:block;margin-bottom:0.5em;">
61
- <%= t('collavre.creatives.index.notion_parent_page', default: 'Parent page:') %>
62
- </label>
63
- <select id="notion-parent-page-select" style="width:100%;padding:0.5em;border:1px solid var(--color-border);border-radius:4px;">
64
- <option value=""><%= t('collavre.creatives.index.notion_loading', default: 'Loading pages...') %></option>
65
- </select>
66
- </div>
67
- </div>
68
-
69
- <div class="notion-wizard-step" id="notion-step-summary" style="display:none;">
70
- <p class="notion-modal-subtext"><%= t('collavre.creatives.index.notion_integration_summary', default: 'Ready to export your creative tree to Notion:') %></p>
71
- <div id="notion-export-summary" style="margin:1em 0;padding:1em;background:var(--color-bg-alt);border-radius:4px;">
72
- <div><strong><%= t('collavre.creatives.index.notion_creative_title', default: 'Root Creative:') %></strong> <span id="notion-creative-title"></span></div>
73
- <div><strong><%= t('collavre.creatives.index.notion_workspace', default: 'Workspace:') %></strong> <span id="notion-workspace-summary"></span></div>
74
- <div><strong><%= t('collavre.creatives.index.notion_export_as', default: 'Export as:') %></strong> <span id="notion-export-type-summary"></span></div>
75
- <div id="notion-parent-summary" style="display:none;"><strong><%= t('collavre.creatives.index.notion_parent_page', default: 'Parent page:') %></strong> <span id="notion-parent-page-summary"></span></div>
76
- <div style="margin-top:0.5em;color:var(--color-text-secondary);font-size:0.9em;"><%= t('collavre.creatives.index.notion_tree_note', default: 'This will export the selected creative and all its descendants as a structured document.') %></div>
77
- </div>
78
- </div>
79
-
80
- <div id="notion-wizard-error" style="display:none;margin:0.5em 0;color:#c0392b;font-weight:bold;"></div>
81
-
82
- <div class="notion-wizard-footer" style="display:flex;justify-content:space-between;gap:0.5em;margin-top:1.5em;">
83
- <button type="button" id="notion-prev-btn" class="btn btn-secondary" style="display:none;"><%= t('app.previous', default: 'Previous') %></button>
84
- <div style="margin-left:auto;display:flex;gap:0.5em;">
85
- <button type="button" id="notion-next-btn" class="btn btn-primary" style="display:none;"><%= t('app.next', default: 'Next') %></button>
86
- <button type="button" id="notion-export-btn" class="btn btn-primary" style="display:none;"><%= t('collavre.creatives.index.notion_export_button', default: 'Export to Notion') %></button>
87
- </div>
88
- </div>
89
- </div>
90
- </div>
@@ -1,29 +0,0 @@
1
- class CreateNotionIntegrations < ActiveRecord::Migration[8.0]
2
- def change
3
- create_table :notion_accounts do |t|
4
- t.references :user, null: false, foreign_key: true, index: { unique: true }
5
- t.string :notion_uid, null: false
6
- t.string :workspace_name
7
- t.string :workspace_id
8
- t.string :bot_id
9
- t.string :token, null: false
10
- t.datetime :token_expires_at
11
- t.timestamps
12
- end
13
-
14
- create_table :notion_page_links do |t|
15
- t.references :creative, null: false, foreign_key: true
16
- t.references :notion_account, null: false, foreign_key: true
17
- t.string :page_id, null: false
18
- t.string :page_title
19
- t.string :page_url
20
- t.string :parent_page_id
21
- t.datetime :last_synced_at
22
- t.timestamps
23
- end
24
-
25
- add_index :notion_accounts, :notion_uid, unique: true
26
- add_index :notion_page_links, :page_id, unique: true
27
- add_index :notion_page_links, [ :creative_id, :page_id ], unique: true, name: "index_notion_links_on_creative_and_page"
28
- end
29
- end
@@ -1,16 +0,0 @@
1
- class CreateNotionBlockLinks < ActiveRecord::Migration[8.0]
2
- def change
3
- create_table :notion_block_links do |t|
4
- t.references :notion_page_link, null: false, foreign_key: true, index: false
5
- t.references :creative, null: false, foreign_key: true, index: false
6
- t.string :block_id, null: false
7
- t.string :content_hash
8
- t.timestamps
9
- end
10
-
11
- add_index :notion_block_links, :notion_page_link_id, name: "index_notion_block_links_on_notion_page_link_id"
12
- add_index :notion_block_links, :creative_id
13
- add_index :notion_block_links, [ :notion_page_link_id, :creative_id ], unique: true, name: "index_notion_block_links_on_page_link_and_creative"
14
- add_index :notion_block_links, [ :notion_page_link_id, :block_id ], unique: true, name: "index_notion_block_links_on_page_link_and_block"
15
- end
16
- end
@@ -1,5 +0,0 @@
1
- class AllowMultipleNotionBlocksPerCreative < ActiveRecord::Migration[8.0]
2
- def change
3
- remove_index :notion_block_links, name: "index_notion_block_links_on_page_link_and_creative"
4
- end
5
- end