collavre_linear 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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/app/controllers/collavre_linear/application_controller.rb +21 -0
  3. data/app/controllers/collavre_linear/auth_controller.rb +135 -0
  4. data/app/controllers/collavre_linear/creatives/integrations_controller.rb +271 -0
  5. data/app/controllers/collavre_linear/webhooks_controller.rb +187 -0
  6. data/app/javascript/collavre_linear.js +199 -0
  7. data/app/jobs/collavre_linear/inbound_apply_job.rb +36 -0
  8. data/app/jobs/collavre_linear/outbound_archive_job.rb +29 -0
  9. data/app/jobs/collavre_linear/outbound_comment_delete_job.rb +41 -0
  10. data/app/jobs/collavre_linear/outbound_comment_sync_job.rb +112 -0
  11. data/app/jobs/collavre_linear/outbound_comment_update_job.rb +55 -0
  12. data/app/jobs/collavre_linear/outbound_sync_job.rb +39 -0
  13. data/app/models/collavre_linear/account.rb +29 -0
  14. data/app/models/collavre_linear/application_record.rb +7 -0
  15. data/app/models/collavre_linear/comment_link.rb +12 -0
  16. data/app/models/collavre_linear/issue_link.rb +20 -0
  17. data/app/models/collavre_linear/project_link.rb +76 -0
  18. data/app/observers/collavre_linear/comment_sync_observer.rb +97 -0
  19. data/app/observers/collavre_linear/creative_sync_observer.rb +162 -0
  20. data/app/services/collavre_linear/client.rb +425 -0
  21. data/app/services/collavre_linear/comment_formatter.rb +34 -0
  22. data/app/services/collavre_linear/comment_syncability.rb +33 -0
  23. data/app/services/collavre_linear/creative_exporter.rb +352 -0
  24. data/app/services/collavre_linear/echo_guard.rb +36 -0
  25. data/app/services/collavre_linear/field_mapper.rb +119 -0
  26. data/app/services/collavre_linear/inbound_applier.rb +599 -0
  27. data/app/services/collavre_linear/o_auth_token_service.rb +163 -0
  28. data/app/views/collavre_linear/auth/setup.html.erb +63 -0
  29. data/app/views/collavre_linear/integrations/_modal.html.erb +200 -0
  30. data/config/initializers/integration_settings.rb +8 -0
  31. data/config/locales/en.yml +49 -0
  32. data/config/locales/ko.yml +49 -0
  33. data/config/routes.rb +22 -0
  34. data/db/migrate/20260701000000_create_linear_accounts.rb +20 -0
  35. data/db/migrate/20260701000001_create_collavre_linear_project_links.rb +18 -0
  36. data/db/migrate/20260701000002_create_collavre_linear_issue_links.rb +22 -0
  37. data/db/migrate/20260701000003_create_collavre_linear_comment_links.rb +17 -0
  38. data/db/migrate/20260701000004_add_last_outbound_at_to_linear_links.rb +6 -0
  39. data/db/migrate/20260701000005_add_webhook_id_to_linear_project_links.rb +5 -0
  40. data/db/migrate/20260701000006_add_lookup_indexes_to_linear_project_links.rb +17 -0
  41. data/db/migrate/20260701000007_enforce_one_project_link_per_creative.rb +19 -0
  42. data/db/migrate/20260701000008_allow_null_webhook_secret_on_linear_project_links.rb +8 -0
  43. data/lib/collavre_linear/engine.rb +122 -0
  44. data/lib/collavre_linear/version.rb +3 -0
  45. data/lib/collavre_linear.rb +5 -0
  46. metadata +115 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inbound webhooks resolve the ProjectLink by team_id and by linear_project_id
4
+ # on every delivery. The pre-existing composite [creative_id, linear_project_id]
5
+ # index can't serve a team_id lookup and doesn't lead with linear_project_id, so
6
+ # both hot-path lookups full-scanned. Add covering single-column indexes.
7
+ class AddLookupIndexesToLinearProjectLinks < ActiveRecord::Migration[8.0]
8
+ def change
9
+ add_index :linear_project_links, :team_id
10
+ # UNIQUE: one Linear project maps to exactly one Collavre root. Inbound
11
+ # webhooks resolve the project via an unscoped find_by(linear_project_id:),
12
+ # so a second link on a disjoint creative would route imports to whichever
13
+ # row the DB returns. The controller's check-then-save can't stop two
14
+ # concurrent link requests from both inserting; enforce it at the DB layer.
15
+ add_index :linear_project_links, :linear_project_id, unique: true
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # One Collavre root maps to exactly one Linear project. The controller's
4
+ # origin_conflict check is check-then-save, so two concurrent link requests for
5
+ # the SAME root to DIFFERENT projects can both pass it and insert — the existing
6
+ # unique indexes don't stop it: linear_project_id is unique per project (not per
7
+ # root) and the composite [creative_id, linear_project_id] permits two different
8
+ # projects on one creative. resolve_project_link then does find_by(creative_id:)
9
+ # and would sync against an arbitrary project. Enforce the invariant at the DB
10
+ # layer: promote the creative_id index to UNIQUE (which subsumes and replaces the
11
+ # now-redundant composite).
12
+ class EnforceOneProjectLinkPerCreative < ActiveRecord::Migration[8.0]
13
+ def change
14
+ remove_index :linear_project_links,
15
+ name: "index_linear_project_links_on_creative_and_project"
16
+ remove_index :linear_project_links, :creative_id
17
+ add_index :linear_project_links, :creative_id, unique: true
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ class AllowNullWebhookSecretOnLinearProjectLinks < ActiveRecord::Migration[8.0]
2
+ # Linear generates the webhook signing secret when the webhook is created and
3
+ # won't let us pick it, so a ProjectLink is created without one and the admin
4
+ # pastes Linear's value in afterward. Relax the original NOT NULL accordingly.
5
+ def change
6
+ change_column_null :linear_project_links, :webhook_secret, true
7
+ end
8
+ end
@@ -0,0 +1,122 @@
1
+ module CollavreLinear
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CollavreLinear
4
+
5
+ # Path this engine is mounted at in the host app (see the
6
+ # `collavre_linear.routes` initializer below). Because the engine is
7
+ # `isolate_namespace`d and detached, its own `url_helpers` generate
8
+ # engine-internal paths WITHOUT this prefix. Anything that needs a URL the
9
+ # host (or an external service like Linear) will actually reach must fold
10
+ # this prefix back in — pass it as `script_name:` to the engine url_helper.
11
+ MOUNT_PATH = "/linear"
12
+
13
+ config.generators do |g|
14
+ g.test_framework :minitest
15
+ end
16
+
17
+ def self.javascript_path
18
+ root.join("app/javascript")
19
+ end
20
+
21
+ def self.stylesheet_path
22
+ root.join("app/assets/stylesheets")
23
+ end
24
+
25
+ config.i18n.load_path += Dir[root.join("config", "locales", "*.yml")]
26
+
27
+ initializer "collavre_linear.routes", before: :add_routing_paths do |app|
28
+ app.routes.append do
29
+ mount CollavreLinear::Engine => CollavreLinear::Engine::MOUNT_PATH, as: :linear_engine
30
+ end
31
+ end
32
+
33
+ initializer "collavre_linear.assets" do |app|
34
+ if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:paths)
35
+ app.config.assets.paths << root.join("app/assets/stylesheets")
36
+ end
37
+ end
38
+
39
+ initializer "collavre_linear.migrations" do |app|
40
+ unless app.root.to_s.match?(root.to_s)
41
+ config.paths["db/migrate"].expanded.each do |expanded_path|
42
+ app.config.paths["db/migrate"] << expanded_path
43
+ end
44
+ end
45
+ end
46
+
47
+ initializer "collavre_linear.register_integration", after: :load_config_initializers do
48
+ Rails.application.config.to_prepare do
49
+ if defined?(Collavre::IntegrationRegistry)
50
+ Collavre::IntegrationRegistry.register(:linear, {
51
+ label: I18n.t("collavre_linear.integration.label", default: "Linear"),
52
+ icon: "linear",
53
+ description: I18n.t("collavre_linear.integration.description", default: "Two-way sync of projects, issues and comments"),
54
+ routes: CollavreLinear::Engine.routes.url_helpers,
55
+ creative_menu_partial: "collavre_linear/integrations/modal"
56
+ })
57
+ end
58
+ end
59
+ end
60
+
61
+ initializer "collavre_linear.user_associations", after: :load_config_initializers do
62
+ Rails.application.config.to_prepare do
63
+ user_class = Collavre.user_class rescue nil
64
+ next unless user_class
65
+
66
+ unless user_class.reflect_on_association(:linear_account)
67
+ user_class.has_one :linear_account,
68
+ class_name: "CollavreLinear::Account",
69
+ foreign_key: :user_id,
70
+ dependent: :destroy
71
+ end
72
+ end
73
+ end
74
+
75
+ initializer "collavre_linear.creative_associations", after: :load_config_initializers do
76
+ Rails.application.config.to_prepare do
77
+ creative_class = Collavre::Creative rescue nil
78
+ next unless creative_class
79
+
80
+ unless creative_class.reflect_on_association(:linear_project_links)
81
+ creative_class.has_many :linear_project_links,
82
+ class_name: "CollavreLinear::ProjectLink",
83
+ foreign_key: :creative_id,
84
+ dependent: :destroy
85
+ end
86
+
87
+ unless creative_class.reflect_on_association(:linear_issue_links)
88
+ creative_class.has_many :linear_issue_links,
89
+ class_name: "CollavreLinear::IssueLink",
90
+ foreign_key: :creative_id,
91
+ dependent: :destroy
92
+ end
93
+
94
+ if creative_class.respond_to?(:register_reserved_metadata_key)
95
+ creative_class.register_reserved_metadata_key("linear")
96
+ end
97
+ end
98
+ end
99
+
100
+ initializer "collavre_linear.creative_sync_observer", after: :load_config_initializers do
101
+ Rails.application.config.to_prepare do
102
+ creative_class = Collavre::Creative rescue nil
103
+ next unless creative_class
104
+
105
+ unless creative_class.include?(CollavreLinear::CreativeSyncObserver)
106
+ creative_class.include CollavreLinear::CreativeSyncObserver
107
+ end
108
+ end
109
+ end
110
+
111
+ initializer "collavre_linear.comment_sync_observer", after: :load_config_initializers do
112
+ Rails.application.config.to_prepare do
113
+ comment_class = Collavre::Comment rescue nil
114
+ next unless comment_class
115
+
116
+ unless comment_class.include?(CollavreLinear::CommentSyncObserver)
117
+ comment_class.include CollavreLinear::CommentSyncObserver
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ module CollavreLinear
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "collavre_linear/version"
2
+ require "collavre_linear/engine"
3
+
4
+ module CollavreLinear
5
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: collavre_linear
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Collavre
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: collavre
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: Plugin engine to connect Collavre creatives with Linear projects, issues
41
+ and comments.
42
+ email:
43
+ - support@collavre.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - app/controllers/collavre_linear/application_controller.rb
49
+ - app/controllers/collavre_linear/auth_controller.rb
50
+ - app/controllers/collavre_linear/creatives/integrations_controller.rb
51
+ - app/controllers/collavre_linear/webhooks_controller.rb
52
+ - app/javascript/collavre_linear.js
53
+ - app/jobs/collavre_linear/inbound_apply_job.rb
54
+ - app/jobs/collavre_linear/outbound_archive_job.rb
55
+ - app/jobs/collavre_linear/outbound_comment_delete_job.rb
56
+ - app/jobs/collavre_linear/outbound_comment_sync_job.rb
57
+ - app/jobs/collavre_linear/outbound_comment_update_job.rb
58
+ - app/jobs/collavre_linear/outbound_sync_job.rb
59
+ - app/models/collavre_linear/account.rb
60
+ - app/models/collavre_linear/application_record.rb
61
+ - app/models/collavre_linear/comment_link.rb
62
+ - app/models/collavre_linear/issue_link.rb
63
+ - app/models/collavre_linear/project_link.rb
64
+ - app/observers/collavre_linear/comment_sync_observer.rb
65
+ - app/observers/collavre_linear/creative_sync_observer.rb
66
+ - app/services/collavre_linear/client.rb
67
+ - app/services/collavre_linear/comment_formatter.rb
68
+ - app/services/collavre_linear/comment_syncability.rb
69
+ - app/services/collavre_linear/creative_exporter.rb
70
+ - app/services/collavre_linear/echo_guard.rb
71
+ - app/services/collavre_linear/field_mapper.rb
72
+ - app/services/collavre_linear/inbound_applier.rb
73
+ - app/services/collavre_linear/o_auth_token_service.rb
74
+ - app/views/collavre_linear/auth/setup.html.erb
75
+ - app/views/collavre_linear/integrations/_modal.html.erb
76
+ - config/initializers/integration_settings.rb
77
+ - config/locales/en.yml
78
+ - config/locales/ko.yml
79
+ - config/routes.rb
80
+ - db/migrate/20260701000000_create_linear_accounts.rb
81
+ - db/migrate/20260701000001_create_collavre_linear_project_links.rb
82
+ - db/migrate/20260701000002_create_collavre_linear_issue_links.rb
83
+ - db/migrate/20260701000003_create_collavre_linear_comment_links.rb
84
+ - db/migrate/20260701000004_add_last_outbound_at_to_linear_links.rb
85
+ - db/migrate/20260701000005_add_webhook_id_to_linear_project_links.rb
86
+ - db/migrate/20260701000006_add_lookup_indexes_to_linear_project_links.rb
87
+ - db/migrate/20260701000007_enforce_one_project_link_per_creative.rb
88
+ - db/migrate/20260701000008_allow_null_webhook_secret_on_linear_project_links.rb
89
+ - lib/collavre_linear.rb
90
+ - lib/collavre_linear/engine.rb
91
+ - lib/collavre_linear/version.rb
92
+ homepage: https://collavre.com
93
+ licenses:
94
+ - AGPL
95
+ metadata:
96
+ homepage_uri: https://collavre.com
97
+ source_code_uri: https://github.com/sh1nj1/plan42
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.6.7
113
+ specification_version: 4
114
+ summary: Linear integration for Collavre
115
+ test_files: []