hephaestus 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/lib/hephaestus/app_builder.rb +5 -2
  4. data/lib/hephaestus/generators/app_generator.rb +28 -28
  5. data/lib/hephaestus/generators/config_generator.rb +2 -3
  6. data/lib/hephaestus/version.rb +1 -1
  7. data/templates/.github/workflows/licenses.yml +1 -1
  8. data/templates/.github/workflows/lint.yml +1 -1
  9. data/templates/.github/workflows/security.yml +1 -1
  10. data/templates/.github/workflows/sorbet.yml +1 -1
  11. data/templates/Gemfile.erb +3 -2
  12. data/templates/app/controllers/app_controller.rb +3 -3
  13. data/templates/app/controllers/concerns/authable.rb +2 -2
  14. data/templates/app/controllers/yetto_controller.rb +11 -0
  15. data/templates/app/jobs/update_yetto_job.rb +6 -4
  16. data/templates/app/lib/body_parameter/yetto_parameters.rb +93 -9
  17. data/templates/app/lib/headers/yetto.rb +3 -1
  18. data/templates/app/lib/path_parameter/yetto_parameters.rb +6 -3
  19. data/templates/app/lib/plug_app/http.rb +3 -0
  20. data/templates/app/lib/plug_app/switches/message_created.jsonc +25 -0
  21. data/templates/app/services/yetto_service.rb +19 -14
  22. data/templates/compose.yml +1 -1
  23. data/templates/config/initializers/000-oj.rb +6 -0
  24. data/templates/config/initializers/environment.rb +5 -1
  25. data/templates/lib/plug_app/schemas/api/2023-03-06/components/parameters/headers/yetto.json +1 -1
  26. data/templates/lib/plug_app/schemas/api/2023-03-06/components/schemas/yetto.json +1 -1
  27. data/templates/lib/plug_app/schemas/api/2023-03-06/openapi.json +4 -4
  28. data/templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/{after_create_message.json → message_created.json} +1 -1
  29. data/templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/{after_create_plug_installation.json → plug_installation_created.json} +1 -1
  30. data/templates/test/controllers/yetto_controller_test.rb +7 -7
  31. data/templates/test/fixtures/files/plug_installation_settings/invalid.json +3 -0
  32. data/templates/test/fixtures/files/plug_installation_settings/valid.json +3 -0
  33. data/templates/test/fixtures/plug_installation_settings/invalid.json +3 -0
  34. data/templates/test/fixtures/plug_installation_settings/valid.json +3 -0
  35. data/templates/test/support/api.rb +6 -4
  36. data/templates/test/support/webmocks/yetto_webmock.rb +36 -17
  37. data/templates/vendor/fly/fly-production.toml +24 -21
  38. data/templates/vendor/fly/fly-staging.toml +24 -21
  39. metadata +11 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b95d21bfd57de5966b0e84d88b1405b86858be61eba98db2540cd4321169fe3c
4
- data.tar.gz: a1a812f73c093ec210675c9f5f736893c0170aece8f06ce92cb43ff9df39467c
3
+ metadata.gz: 514b3a3d6af4cd4f265334e540510abeba70de3758c4c92e6dd3a6297c6e428b
4
+ data.tar.gz: d852903702132b3add71f638494809ad6bc167bbbb3f83ec625bd21999cbd081
5
5
  SHA512:
6
- metadata.gz: d967799d74078769fe825e5b03cfae506da34b023ff2e07edbe97ec91283592a50e664d280a1c2394ceefdd2047952ca36628e602789d224758d9bc91b45952b
7
- data.tar.gz: '08804720c39e023bfd1abc2ac87de38fc9badf9509fc8b3f161f0528862653bfb18727da01aab24ce56012e4346ef6abb2f6f78746c1e681a533787b49df2bd0'
6
+ metadata.gz: 35438e55bf05ced5fb9e90002f01ada1ee1b3ae9d8af941bf910a169c9d0ac45ace87f0ec8c086640b2c5c50d68bee7db8569c33bc92fc56cac5d83abe05fd77
7
+ data.tar.gz: 0b8d7b5cd5b5aa26328c704e44ed0385ba0865719d5e48440a50484475b7e0dc0644939ad94944111d9e1cdcbf3ba7ac560672675772ea8a0e6b6265c1206eb1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,5 @@
1
+ ## [v0.3.1] - 18-09-2023
2
+ **Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.3.0...v0.3.1
1
3
  ## [v0.3.0] - 12-09-2023
2
4
  ## What's Changed
3
5
  * Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/yettoapp/hephaestus/pull/8
@@ -158,11 +158,14 @@ module Hephaestus
158
158
  def replace_generic_variables
159
159
  say(set_color("Replacing generic variable names...", :cyan))
160
160
  replace_in_files(destination_root, /App(?!lication)/, short_app_name.capitalize)
161
- replace_in_files(destination_root, %r{(?<!yetto)app(?!lication|/|roximate)}, short_app_name.downcase)
161
+ replace_in_files(destination_root, %r{(?<!yetto|plugs\.yetto\.)app(?!lication|/|roximate)}, short_app_name.downcase)
162
162
  replace_in_files(destination_root, "PlugApp", app_name.underscore.camelcase)
163
163
  replace_in_files(destination_root, "plug-app", app_name.dasherize)
164
+ replace_in_files("destination_root}/.github/workflows/test.yml", "plug-app", app_name.dasherize)
164
165
  replace_in_files(destination_root, "PLUG_APP", app_name.underscore.upcase)
165
- replace_in_files(destination_root, "plug_app", app_name.underscore)
166
+ # these usually take the form of "plug_app", as a hack to prevent them from changing by the other
167
+ # rename methods...I think. I actually can't say for sure I remember what it's for.
168
+ replace_in_files(destination_root, "plug_", "")
166
169
 
167
170
  # this is erroneously changed
168
171
  replace_in_file("script/server", "/usr/src/#{short_app_name.downcase}", "/usr/src/app")
@@ -28,37 +28,10 @@ module Hephaestus
28
28
  super
29
29
  end
30
30
 
31
- # NOTE: this function name is important as it overrides
32
- # a Rails function of the same name (that function performs work
33
- # that we don't want to do)
34
- def leftovers
35
- build(:replace_generic_variables)
36
-
37
- say(set_color("Generating `hephaestus:license`...", :cyan))
38
- capture_stdout do
39
- generate("hephaestus:license")
40
- end
41
- say(set_color("Generating `hephaestus:rubocop`...", :cyan))
42
- capture_stdout do
43
- generate("hephaestus:rubocop")
44
- end
45
- say(set_color("Generating `hephaestus:sorbet`...", :cyan))
46
- capture_stdout do
47
- generate("hephaestus:sorbet")
48
- end
49
- say(set_color("Creating first commit...", :cyan))
50
- capture_stdout do
51
- invoke(:commit)
52
- end
53
-
54
- build(:restore_gemfile)
55
- invoke(:outro)
56
- end
57
-
58
31
  def hephaestus_customization
59
32
  say(set_color("Invoking Hephaestus customizations...", :cyan))
60
33
 
61
- run("rails app:update:bin", capture: true)
34
+ run("rails app:update:bin")
62
35
  invoke(:customize_gemfile)
63
36
 
64
37
  invoke(:copy_github_actions)
@@ -171,6 +144,33 @@ module Hephaestus
171
144
  end
172
145
  end
173
146
 
147
+ # NOTE: this function name is important as it overrides
148
+ # a Rails function of the same name (that function performs work
149
+ # that we don't want to do)
150
+ def leftovers
151
+ build(:replace_generic_variables)
152
+
153
+ say(set_color("Generating `hephaestus:license`...", :cyan))
154
+ capture_stdout do
155
+ generate("hephaestus:license")
156
+ end
157
+ say(set_color("Generating `hephaestus:rubocop`...", :cyan))
158
+ capture_stdout do
159
+ generate("hephaestus:rubocop")
160
+ end
161
+ say(set_color("Generating `hephaestus:sorbet`...", :cyan))
162
+ capture_stdout do
163
+ generate("hephaestus:sorbet")
164
+ end
165
+ say(set_color("Creating first commit...", :cyan))
166
+ capture_stdout do
167
+ invoke(:commit)
168
+ end
169
+
170
+ build(:restore_gemfile)
171
+ invoke(:outro)
172
+ end
173
+
174
174
  def commit
175
175
  run("git add .")
176
176
  run("git commit -m 'Initial commit'", capture: true)
@@ -89,11 +89,10 @@ module Hephaestus
89
89
 
90
90
  # events into the plug, usually from yetto
91
91
  get "/api/2023-03-06/settings", to: "settings#new"
92
- post "/api/2023-03-06/:event/:record_type", to: "yetto#event"
92
+ post "/api/2023-03-06/:record_type/:event", to: "yetto#event"
93
93
 
94
94
  # inbound message
95
- post "/app/2023-03-06/webhook/:organization_id/:inbox_id/:plug_installation_id", to: "app#webhook"
96
- # post "/app/2023-03-06/webhook/inbound", to: "app#process_inbound" # for generic inbound pings
95
+ post "/$app/2023-03-06/webhook/inbound", to: "app#webhook"
97
96
 
98
97
  # Staff pages
99
98
  get "staff", to: "staff#index"
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Hephaestus
5
- VERSION = "0.3.1"
5
+ VERSION = "0.4.0"
6
6
  RAILS_VERSION = "~> 7.0"
7
7
  RUBY_VERSION = File
8
8
  .read("#{File.dirname(__FILE__)}/../../.ruby-version")
@@ -13,7 +13,7 @@ jobs:
13
13
  verify:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@v3
16
+ - uses: actions/checkout@v4
17
17
  with:
18
18
  ref: ${{ github.head_ref }}
19
19
  token: ${{ secrets.GH_SISYPHUS_YETTO_REPO_TOKEN }}
@@ -12,7 +12,7 @@ jobs:
12
12
  rubocop:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v3
15
+ - uses: actions/checkout@v4
16
16
 
17
17
  - uses: yettoapp/actions/pr-contains-files@main
18
18
  id: pr_contains_ruby
@@ -12,4 +12,4 @@ jobs:
12
12
  ruby:
13
13
  uses: yettoapp/actions/.github/workflows/ruby_security_checks.yml@main
14
14
  secrets:
15
- token: ${{ secrets.GITHUB_TOKEN }}
15
+ gh_token: ${{ secrets.GH_SISYPHUS_YETTO_REPO_TOKEN }}
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
 
13
13
  steps:
14
- - uses: actions/checkout@v3
14
+ - uses: actions/checkout@v4
15
15
  with:
16
16
  ref: ${{ github.head_ref }}
17
17
  token: ${{ secrets.GH_SISYPHUS_YETTO_REPO_TOKEN }}
@@ -44,6 +44,9 @@ gem "opentelemetry-instrumentation-active_job", "~> 0.5"
44
44
  gem "opentelemetry-instrumentation-redis", "~> 0.25"
45
45
  gem "opentelemetry-instrumentation-sidekiq", "~> 0.23"
46
46
 
47
+ # massively improved JSON parsing
48
+ gem "oj", "~> 3.16"
49
+
47
50
  # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
48
51
  gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
49
52
 
@@ -113,5 +116,3 @@ group :ci do
113
116
  gem "brakeman", "~> 5.3"
114
117
  gem "bundle-audit", "~> 0.1"
115
118
  end
116
-
117
- gem "hephaestus", group: [:development, :test]
@@ -8,12 +8,12 @@ class AppController < ApplicationController
8
8
  include PathParameter::AppParameters
9
9
  include BodyParameter::AppParameters
10
10
 
11
- before_action :from__app_?
11
+ before_action :from_app?
12
12
 
13
13
  # Inbound message from ${App}
14
14
  def webhook
15
- # Error if necesary parameters from ${App} are missing
16
- return bad_request unless has_inbound__app__params?
15
+ # Error if necessary parameters from ${App} are missing
16
+ return bad_request unless has_inbound_app_params?
17
17
 
18
18
  response = YettoService.get_plug_installation(pparam_organization_id, pparam_inbox_id, pparam_plug_installation_id)
19
19
 
@@ -26,8 +26,8 @@ module Authable
26
26
 
27
27
  # status is annoyingly set to 401, but we want
28
28
  # to hide that an issue exists
29
- response.status = PlugApp::HTTP::BAD_REQUEST_I
30
- response.body = ::ErrorSerializer.format(PlugApp::HTTP::BAD_REQUEST)
29
+ self.status = PlugApp::HTTP::BAD_REQUEST_I
30
+ self.response_body = ::ErrorSerializer.format(PlugApp::HTTP::BAD_REQUEST)
31
31
  end
32
32
 
33
33
  sig { void }
@@ -3,6 +3,7 @@
3
3
 
4
4
  class YettoController < ApplicationController
5
5
  include BodyParameter::YettoParameters
6
+ include PathParameter
6
7
  include PathParameter::YettoParameters
7
8
  include Authable
8
9
 
@@ -10,11 +11,21 @@ class YettoController < ApplicationController
10
11
 
11
12
  before_action :from_yetto?
12
13
 
14
+ AFTER_CREATE_MESSAGE_SWITCH = JSON.parse(Rails.root.join("app/lib/plug_app/switches/message_created.jsonc").read)
15
+
13
16
  def event
14
17
  case pparam_yetto_event
15
18
  when Headers::Yetto::EVENT_AFTER_CREATE
16
19
  case pparam_yetto_record_type
17
20
  when Headers::Yetto::RECORD_TYPE_PLUG_INSTALLATION
21
+ create_inbox_switch_data = {
22
+ type: "create_inbox_switch",
23
+ inbox: { id: bparam_inbox_id },
24
+ plug_installation: { id: bparam_plug_installation_id },
25
+ payload: AFTER_CREATE_MESSAGE_SWITCH,
26
+ }
27
+
28
+ UpdateYettoJob.perform_later(create_inbox_switch_data)
18
29
 
19
30
  no_content
20
31
  when Headers::Yetto::RECORD_TYPE_MESSAGE
@@ -5,22 +5,24 @@
5
5
  # This can be used to update installation data or message data
6
6
 
7
7
  class UpdateYettoJob < ApplicationJob
8
- queue_as :default
8
+ queue_as :update_yetto
9
9
 
10
10
  def perform(params)
11
11
  type = params.delete(:type)
12
12
 
13
- organization_id = params.fetch(:organization, {}).fetch(:id, nil)
14
13
  inbox_id = params.fetch(:inbox, {}).fetch(:id, nil)
15
14
  plug_installation_id = params.fetch(:plug_installation, {}).fetch(:id, nil)
15
+ message_id = params.fetch(:message, {}).fetch(:id, nil)
16
16
 
17
17
  case type
18
18
  when "update_plug_installation"
19
19
  YettoService.update_installation(plug_installation_id, params)
20
20
  when "create_inbox_switch"
21
21
  YettoService.create_inbox_switch(inbox_id, plug_installation_id, params)
22
- when "create_message"
23
- YettoService.create_message(inbox_id, plug_installation_id, params)
22
+ when "create_message_reply"
23
+ YettoService.create_message_reply(message_id, plug_installation_id, params)
24
+ when "add_message_metadata"
25
+ YettoService.update_message(message_id, plug_installation_id, params)
24
26
  end
25
27
  end
26
28
  end
@@ -5,25 +5,47 @@ module BodyParameter
5
5
  module YettoParameters
6
6
  extend T::Sig
7
7
 
8
- sig { returns(T::Boolean) }
9
- def has_update_plug_installation_params?
10
- return false if update_plug_installation_params.blank?
8
+ sig { returns(String) }
9
+ def bparam_organization_id
10
+ plug_installation_params[:organization][:id]
11
+ end
12
+
13
+ sig { returns(String) }
14
+ def bparam_inbox_id
15
+ plug_installation_params[:inbox][:id]
16
+ end
17
+
18
+ sig { returns(String) }
19
+ def bparam_plug_installation_id
20
+ plug_installation_params[:plug_installation][:id]
21
+ end
22
+
23
+ sig { returns(String) }
24
+ def bparam_plug_id
25
+ plug_installation_params[:plug][:id]
26
+ end
11
27
 
12
- true
28
+ sig { returns(T::Hash[Symbol, String]) }
29
+ def bparam_plug_installation_settings
30
+ plug_installation_params[:plug_installation][:settings]
13
31
  end
14
32
 
15
33
  sig { returns(T::Hash[Symbol, String]) }
16
- def update_plug_installation_params
34
+ def plug_installation_params
17
35
  return {} if params.blank?
18
36
 
19
- return {} if (plug_installation_id = params.fetch(:plug_installation, {}).fetch(:id, "")).blank?
20
- return {} if (organization_id = params.fetch(:organization, {}).fetch(:id, "")).blank?
21
- return {} if (plug_id = params.fetch(:plug, {}).fetch(:id, "")).blank?
22
- return {} if (inbox_id = params.fetch(:inbox, {}).fetch(:id, "")).blank?
37
+ plug_installation = params.fetch(:plug_installation, {})
38
+ plug_installation_id = plug_installation.fetch(:id, "")
39
+ settings = plug_installation.fetch(:settings, {}).to_unsafe_hash
40
+
41
+ organization_id = params.fetch(:organization, {}).fetch(:id, "")
42
+ plug_id = params.fetch(:plug, {}).fetch(:id, "")
43
+ inbox_id = params.fetch(:inbox, {}).fetch(:id, "")
23
44
 
24
45
  {
25
46
  plug_installation: {
26
47
  id: plug_installation_id,
48
+ settings: settings,
27
49
  },
28
50
  organization: {
29
51
  id: organization_id,
@@ -36,5 +58,67 @@ module BodyParameter
36
58
  },
37
59
  }
38
60
  end
61
+
62
+ sig { returns(String) }
63
+ def bparam_message_id
64
+ message_params[:message][:id]
65
+ end
66
+
67
+ sig { returns(String) }
68
+ def bparam_message_text_content
69
+ message_params[:message][:text_content]
70
+ end
71
+
72
+ sig { returns(String) }
73
+ def bparam_message_conversation_id
74
+ message_params[:message][:conversation][:id]
75
+ end
76
+
77
+ sig { returns(String) }
78
+ def bparam_message_conversation_title
79
+ message_params[:message][:conversation][:title]
80
+ end
81
+
82
+ sig { returns(String) }
83
+ def bparam_message_author_name
84
+ message_params[:message].fetch(:author, {}).fetch(:name, "Noone")
85
+ end
86
+
87
+ sig { returns(T::Hash[Symbol, String]) }
88
+ def bparam_message_metadata
89
+ message_params[:message][:metadata]
90
+ end
91
+
92
+ sig { returns(T::Hash[Symbol, String]) }
93
+ def message_params
94
+ return {} if params.blank?
95
+
96
+ message = params.fetch(:message, {})
97
+ message_id = message.fetch(:id, "")
98
+ text_content = message.fetch(:text_content, "")
99
+ conversation = message.fetch(:conversation, {})
100
+ conversation_id = conversation.fetch(:id, "")
101
+ title = conversation.fetch(:title, "")
102
+ created_by_user = message.fetch(:created_by_user, {})
103
+ created_by_plug = message.fetch(:created_by_plug, {})
104
+ author = (created_by_user.presence || created_by_plug)
105
+ name = author.fetch(:name, "")
106
+ metadata = message.fetch(:metadata, {}).to_unsafe_hash
107
+
108
+ {
109
+ message: {
110
+ id: message_id,
111
+ text_content: text_content,
112
+ conversation: {
113
+ id: conversation_id,
114
+ title: title,
115
+ },
116
+ author: {
117
+ name: name,
118
+ },
119
+ metadata: metadata,
120
+ },
121
+ }
122
+ end
39
123
  end
40
124
  end
@@ -6,7 +6,9 @@ module Headers
6
6
  YETTO_DELIVERY_ID = "HTTP_X_YETTO_DELIVERY_ID"
7
7
 
8
8
  HEADER_EVENT = "HTTP_X_YETTO_EVENT"
9
- EVENT_AFTER_CREATE = "after_create"
9
+ EVENT_AFTER_CREATE = "created"
10
+ EVENT_AFTER_UPDATE = "updated"
11
+ EVENT_AFTER_DESTROY = "destroyed"
10
12
 
11
13
  HEADER_RECORD_TYPE = "HTTP_X_YETTO_RECORD_TYPE"
12
14
  RECORD_TYPE_PLUG_INSTALLATION = "plug_installation"
@@ -15,11 +15,14 @@ module PathParameter
15
15
  yetto_path_params.fetch(:record_type, "")
16
16
  end
17
17
 
18
- sig { returns(ActionController::Parameters) }
18
+ sig { returns(T::Hash[String, String]) }
19
19
  def yetto_path_params
20
- return ActionController::Parameters.new if params.blank?
20
+ return {} if path_parameters.blank?
21
21
 
22
- params.permit(:event, :record_type)
22
+ {
23
+ event: path_parameters.fetch(:event, ""),
24
+ record_type: path_parameters.fetch(:record_type, ""),
25
+ }
23
26
  end
24
27
  end
25
28
  end
@@ -13,6 +13,9 @@ module PlugApp
13
13
  NO_CONTENT = "No Content"
14
14
  NO_CONTENT_I = 204
15
15
 
16
+ FOUND = "Found"
17
+ FOUND_I = 302
18
+
16
19
  NOT_FOUND = "Not Found"
17
20
  NOT_FOUND_I = 404
18
21
  BAD_REQUEST = "Bad Request"
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "Send internal comments to App",
3
+ "configuration": {
4
+ "version": "2023-03-06",
5
+ "events": {
6
+ "message.created": {
7
+ "conditions": {
8
+ "if": "{% data.message.visibility == 'internal' and data.message.text_content contains '@App' %}"
9
+ },
10
+ "actions": [
11
+ {
12
+ "name": "Send to App",
13
+ "uses": "send_to_plug",
14
+ "with": {
15
+ "plug_id": "{{ data.switch.created_by_plug.id }}",
16
+ "message": "{{ data.message }}",
17
+ "plug_installation": "{{ data.plug_installation }}",
18
+ "inbox": "{{ data.inbox }}"
19
+ }
20
+ }
21
+ ]
22
+ }
23
+ }
24
+ }
25
+ }
@@ -6,48 +6,53 @@ class YettoService
6
6
  Httpsensible::Client.new(user_agent: "PlugApp/#{PlugApp::Application::GIT_SHA}")
7
7
  end
8
8
 
9
- PROTOCOL = Rails.env.development? ? "http" : "https"
9
+ # explicitly different than what's in environment.rb, because local
10
+ # Yetto expects HTTP; environment.rb can use HTTPS because it passes through ngrok.io
11
+ PROTOCOL = Rails.env.development? ? "http://" : "https://"
10
12
  YETTO_API_VERSION_TLD = "#{PROTOCOL}://#{YETTO_API_TLD}/#{YETTO_API_VERSION}"
11
13
  JWT_ALGORITHM = "RS256"
12
14
 
13
15
  class << self
14
16
  def perform_token_exchange(plug_installation_id)
15
17
  encoded_jwt = Httpsensible::JWT.encode_jwt(YETTO_PLUG_PEM, YETTO_PLUG_ID)
16
- response = yetto_client.with(headers: { "Authorization" => "Bearer #{encoded_jwt}" }).post("#{YETTO_API_VERSION_TLD}/plugs/installations/#{plug_installation_id}/access_tokens")
18
+ response = yetto_client.with_headers({ "Authorization" => "Bearer #{encoded_jwt}" }).post("#{YETTO_API_VERSION_TLD}/plug/installations/#{plug_installation_id}/access_tokens")
17
19
  body = response.parsed_json_body
18
20
  body["token"]
19
21
  end
20
22
 
21
23
  def get_plug_installation(plug_installation_id)
22
24
  token = perform_token_exchange(plug_installation_id)
23
- yetto_client.with(headers: { "Authorization" => "Bearer #{token}" }).get("#{YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
25
+ yetto_client.with_headers("Authorization" => "Bearer #{token}").get("#{YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
24
26
  end
25
27
 
26
28
  def update_installation(plug_installation_id, params)
27
29
  plug_installation = {}
28
- plug_installation[:settings] = params[:plug_installation].fetch(:settings, {})
29
- plug_installation[:credentials] = params[:plug_installation].fetch(:credentials, {})
30
+ plug_installation[:settings] = params.fetch(:settings, {})
31
+ plug_installation[:credentials] = params.fetch(:credentials, {})
30
32
 
31
33
  token = perform_token_exchange(plug_installation_id)
32
- yetto_client.with(headers: { "Authorization" => "Bearer #{token}" }).patch("#{YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}", json: plug_installation)
34
+ yetto_client.with_headers("Authorization" => "Bearer #{token}").patch("#{YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}", json: plug_installation)
33
35
  end
34
36
 
35
37
  def create_inbox_switch(inbox_id, plug_installation_id, params)
36
- inbox_id = params.fetch(:inbox).fetch(:id)
38
+ payload = params[:payload]
39
+ token = perform_token_exchange(plug_installation_id)
37
40
 
38
- payload = {
39
- name: "After install",
40
- }
41
+ yetto_client.with_headers("Authorization" => "Bearer #{token}").post("#{YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/switches", json: payload)
42
+ end
41
43
 
44
+ def update_message(message_id, plug_installation_id, params)
45
+ payload = params[:payload]
42
46
  token = perform_token_exchange(plug_installation_id)
43
- yetto_client.with(headers: { "Authorization" => "Bearer #{token}" }).post("#{YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/switches", json: payload)
47
+
48
+ yetto_client.with_headers("Authorization" => "Bearer #{token}").patch("#{YETTO_API_VERSION_TLD}/messages/#{message_id}", json: payload)
44
49
  end
45
50
 
46
- def create_message(inbox_id, plug_installation_id, params)
51
+ def create_message_reply(message_id, plug_installation_id, params)
47
52
  payload = params[:payload]
48
-
49
53
  token = perform_token_exchange(plug_installation_id)
50
- yetto_client.with(headers: { "Authorization" => "Bearer #{token}" }).post("#{YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/messages", json: payload)
54
+
55
+ yetto_client.with_headers("Authorization" => "Bearer #{token}").post("#{YETTO_API_VERSION_TLD}/messages/#{message_id}/replies", json: payload)
51
56
  end
52
57
  end
53
58
  end
@@ -4,4 +4,4 @@ services:
4
4
  redis-db:
5
5
  image: redis:7-bookworm
6
6
  ports:
7
- - "6389:6379"
7
+ - "6402:6402"
@@ -0,0 +1,6 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ Oj.default_options = { mode: :rails }
5
+
6
+ Oj.optimize_rails
@@ -16,7 +16,7 @@ YETTO_API_VERSION = ENV.fetch("YETTO_API_VERSION", "2023-03-06")
16
16
 
17
17
  SLACK_LOG_URL = Rails.application.credentials.fetch(:SLACK_LOG_URL, ENV.fetch("SLACK_LOG_URL", "https://slack.com/the_log_room"))
18
18
 
19
- PROTOCOL = Rails.env.development? ? "http://" : "https://"
19
+ PROTOCOL = "https"
20
20
  PLUG_APP_URL = if Rails.env.production?
21
21
  "app.plugs.yetto.app"
22
22
  elsif Rails.env.staging?
@@ -34,3 +34,7 @@ YETTO_PLUG_ID = Rails.application.credentials.fetch(:YETTO_PLUG_ID, ENV.fetch("Y
34
34
  def productionish?
35
35
  Rails.env.production? || Rails.env.staging?
36
36
  end
37
+
38
+ def print_user_api_errors?
39
+ (Rails.env.development? || Rails.env.staging?) || ENV.fetch("DEBUG", false)
40
+ end
@@ -6,7 +6,7 @@
6
6
  "required": true,
7
7
  "schema": {
8
8
  "type": "string",
9
- "pattern": "^after_{create|update|destroy}$"
9
+ "pattern": "^{created|updated|destroyed}$"
10
10
  }
11
11
  },
12
12
  "RecordType": {
@@ -98,7 +98,7 @@
98
98
  },
99
99
  "additionalProperties": false
100
100
  },
101
- "CreateApp": {
101
+ "ConfigureApp": {
102
102
  "type": "object",
103
103
  "required": [
104
104
  "plug_installation",
@@ -17,11 +17,11 @@
17
17
  }
18
18
  ],
19
19
  "paths": {
20
- "/api/2023-03-06/after_create/plug_installation": {
21
- "$ref": "paths/yetto/after_create_plug_installation.json"
20
+ "/api/2023-03-06/plug_installation/created": {
21
+ "$ref": "paths/yetto/plug_installation_created.json"
22
22
  },
23
- "/api/2023-03-06/after_create/message": {
24
- "$ref": "paths/yetto/after_create_message.json"
23
+ "/api/2023-03-06/message/created": {
24
+ "$ref": "paths/yetto/message_created.json"
25
25
  },
26
26
  "/app/2023-03-06/{plugInstallationId}": {
27
27
  "$ref": "paths/app.json"
@@ -4,7 +4,7 @@
4
4
  "Yetto"
5
5
  ],
6
6
  "description": "After a message is created in Yetto, this delivers it.",
7
- "operationId": "Create a new App",
7
+ "operationId": "Create a new message",
8
8
  "requestBody": {
9
9
  "required": true,
10
10
  "content": {
@@ -10,7 +10,7 @@
10
10
  "content": {
11
11
  "application/json": {
12
12
  "schema": {
13
- "$ref": "../../components/schemas/yetto.json#/CreateAppSwitch"
13
+ "$ref": "../../components/schemas/yetto.json#/ConfigureApp"
14
14
  }
15
15
  }
16
16
  }
@@ -60,37 +60,37 @@ class YettoControllerTest < ActionDispatch::IntegrationTest
60
60
 
61
61
  def headers(body)
62
62
  {
63
- Headers::Yetto::HEADER_EVENT => "after_create",
63
+ Headers::Yetto::HEADER_EVENT => "created",
64
64
  Headers::Yetto::HEADER_RECORD_TYPE => "plug_installation",
65
65
  Headers::Yetto::HEADER_SIGNATURE => yetto_auth_header(body),
66
66
  }
67
67
  end
68
68
 
69
69
  test "it handles null headers" do
70
- api(:post, "/after_create/plug_installation", headers: nil)
70
+ api(:post, "/plug_installation/created", headers: nil)
71
71
 
72
72
  assert_response :bad_request
73
73
  end
74
74
 
75
75
  test "it handles missing headers" do
76
- api(:post, "/after_create/plug_installation", headers: {})
76
+ api(:post, "/plug_installation/created", headers: {})
77
77
 
78
78
  assert_response :bad_request
79
79
  end
80
80
 
81
81
  test "it handles incorrect headers" do
82
- api(:post, "/after_create/plug_installation", headers: { "X-Yetto-Signature" => "Basic jabroni:lies" })
82
+ api(:post, "/plug_installation/created", headers: { "X-Yetto-Signature" => "Basic jabroni:lies" })
83
83
 
84
84
  assert_response :bad_request
85
85
 
86
- api(:post, "/after_create/plug_installation", headers: { "X-Yetto-Signature" => "sha256=123456" })
86
+ api(:post, "/plug_installation/created", headers: { "X-Yetto-Signature" => "sha256=123456" })
87
87
 
88
88
  assert_response :bad_request
89
89
  end
90
90
 
91
91
  test "it handles missing body" do
92
92
  body = {}
93
- api(:post, "/after_create/plug_installation", headers: headers(body), body: body)
93
+ api(:post, "/plug_installation/created", headers: headers(body), body: body)
94
94
 
95
95
  assert_response :bad_request
96
96
  end
@@ -122,7 +122,7 @@ class YettoControllerTest < ActionDispatch::IntegrationTest
122
122
  id: "inbx_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}",
123
123
  },
124
124
  }
125
- api(:post, "/after_create/plug_installation", headers: headers_with_one_body, body: body_two)
125
+ api(:post, "/plug_installation/created", headers: headers_with_one_body, body: body_two)
126
126
 
127
127
  assert_response :bad_request
128
128
  end
@@ -0,0 +1,3 @@
1
+ {
2
+ "token_type": "bearer"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "github_installations": "gh_installation_id"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "token_type": "bearer"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "github_installations": "gh_installation_id"
3
+ }
@@ -5,11 +5,11 @@ module API
5
5
  module TestHelpers
6
6
  include Rack::Test::Methods
7
7
 
8
- def plug(method, path, headers: {}, version: nil, body: {})
8
+ def plug(method, path, headers: {}, version: nil, body: {}, parse: true)
9
9
  version ||= PlugApp::CURRENT_VERSION
10
10
  prepended_path = prepend_plug_path(version, path)
11
11
 
12
- http_call(method, prepended_path, headers: headers, version: version, body: body)
12
+ http_call(method, prepended_path, headers: headers, version: version, body: body, parse: parse)
13
13
  end
14
14
 
15
15
  def api(method, path, headers: {}, version: nil, body: {})
@@ -19,7 +19,7 @@ module API
19
19
  http_call(method, prepended_path, headers: headers, version: version, body: body)
20
20
  end
21
21
 
22
- def http_call(method, path, headers: {}, version: nil, body: {})
22
+ def http_call(method, path, headers: {}, version: nil, body: {}, parse: true)
23
23
  # explicitly assert headers cannot be nil
24
24
  if headers.nil?
25
25
  send(method, path, body.to_json)
@@ -29,7 +29,7 @@ module API
29
29
  send(method, path, body.to_json, headers)
30
30
  end
31
31
 
32
- JSON.parse(last_response.body) if last_response.body.present?
32
+ JSON.parse(last_response.body) if last_response.body.present? && parse
33
33
  end
34
34
 
35
35
  def assert_response(expected_status, expected_body = nil)
@@ -42,6 +42,8 @@ module API
42
42
  PlugApp::HTTP::OK_I
43
43
  when :created
44
44
  PlugApp::HTTP::CREATED_I
45
+ when :redirect
46
+ PlugApp::HTTP::FOUND_I
45
47
  when :no_content
46
48
  PlugApp::HTTP::NO_CONTENT_I
47
49
  when :bad_request
@@ -8,28 +8,30 @@ module Webmocks
8
8
  end
9
9
 
10
10
  def assert_requested_post_access_token(plug_installation_id)
11
- assert_requested(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/plugs/installations/#{plug_installation_id}/access_tokens")
11
+ assert_requested(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/plug/installations/#{plug_installation_id}/access_tokens")
12
12
  end
13
13
 
14
14
  def stub_post_access_token(plug_installation_id)
15
15
  response = {
16
16
  token: Faker::Alphanumeric.alphanumeric(number: 26).upcase,
17
17
  }
18
- stub_request(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/plugs/installations/#{plug_installation_id}/access_tokens")
18
+ stub_request(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/plug/installations/#{plug_installation_id}/access_tokens")
19
19
  .to_return(
20
20
  body: response.to_json,
21
21
  )
22
22
  end
23
23
 
24
- def assert_requested_get_plug_installation(plug_installation_id)
24
+ def assert_requested_get_inbox_plug_installation(plug_installation_id)
25
25
  assert_requested_post_access_token(plug_installation_id)
26
26
  assert_requested(:get, "#{::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
27
27
  end
28
28
 
29
- def stub_get_plug_installation(plug_installation_id, response = nil, status: 200)
29
+ def stub_get_inbox_plug_installation(organization_id, inbox_id, plug_installation_id, response = {}, valid: true, status: 200, expires_at: 8.hours.from_now)
30
30
  stub_post_access_token(plug_installation_id)
31
31
 
32
- response ||= {
32
+ github_installations = JSON.parse(file_fixture_path("plug_installation_settings", valid ? "valid.json" : "invalid.json").read).deep_symbolize_keys
33
+
34
+ response = {
33
35
  installed_on_inbox: {
34
36
  id: inbox_id,
35
37
  organization: {
@@ -40,10 +42,13 @@ module Webmocks
40
42
  plug: {
41
43
  id: "plg_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}",
42
44
  },
43
- settings: {
44
- from_email: "new@from.company",
45
+ settings: github_installations,
46
+ credentials: {
47
+ access_token: Faker::Alphanumeric.alphanumeric(number: 26).upcase,
48
+ refresh_access_token: "refresher",
49
+ expires_at: expires_at.iso8601,
45
50
  },
46
- }
51
+ }.merge(response)
47
52
 
48
53
  stub_request(:get, "#{::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
49
54
  .to_return(
@@ -73,16 +78,34 @@ module Webmocks
73
78
  )
74
79
  end
75
80
 
76
- def assert_requested_create_message(inbox_id)
81
+ def assert_requested_create_message(plug_installation_id, message_id)
77
82
  assert_requested_post_access_token(plug_installation_id)
78
83
 
79
- assert_requested(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/messages")
84
+ assert_requested(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}/replies")
80
85
  end
81
86
 
82
- def stub_create_message(inbox_id, payload)
87
+ def stub_create_message(plug_installation_id, message_id, payload)
83
88
  stub_post_access_token(plug_installation_id)
84
89
 
85
- stub_request(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/messages")
90
+ stub_request(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}/replies")
91
+ # .with(
92
+ # body: payload,
93
+ # )
94
+ # .to_return(
95
+ # status: 200,
96
+ # headers: { content_type: "application/json; charset=utf-8" },
97
+ # body: {}.to_json,
98
+ # )
99
+ end
100
+
101
+ def assert_requested_update_message_metadata(plug_installation_id, message_id)
102
+ assert_requested_post_access_token(plug_installation_id)
103
+ assert_requested(:patch, "#{::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}")
104
+ end
105
+
106
+ def stub_update_message_metadata(plug_installation_id, message_id, payload)
107
+ stub_post_access_token(plug_installation_id)
108
+ stub_request(:patch, "#{::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}")
86
109
  .with(
87
110
  body: payload,
88
111
  )
@@ -98,13 +121,9 @@ module Webmocks
98
121
  assert_requested(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/switches")
99
122
  end
100
123
 
101
- def stub_create_inbox_switch(inbox_id, plug_installation_id, params)
124
+ def stub_create_inbox_switch(inbox_id, plug_installation_id, payload)
102
125
  stub_post_access_token(plug_installation_id)
103
126
 
104
- payload = {
105
- name: "After install",
106
- }
107
-
108
127
  stub_request(:post, "#{::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/switches")
109
128
  .with(
110
129
  body: payload,
@@ -7,32 +7,35 @@ app = "plug-app-production"
7
7
  primary_region = "iad"
8
8
 
9
9
  [env]
10
- RAILS_ENV = "production"
11
- LD_PRELOAD_PATH = "/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
10
+ RAILS_ENV = "production"
11
+ LD_PRELOAD_PATH = "/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
12
12
 
13
13
  [processes]
14
- web = "./bin/rails server"
15
- worker = "bundle exec sidekiq"
14
+ web = "./bin/rails server"
15
+ worker = "bundle exec sidekiq"
16
16
 
17
17
  [http_service]
18
- internal_port = 3000
19
- force_https = true
20
- auto_stop_machines = true
21
- auto_start_machines = true
22
- min_machines_running = 1
23
- processes = ["web"]
24
- [http_service.concurrency]
25
- type = "requests"
26
- soft_limit = 200
27
- hard_limit = 250
18
+ internal_port = 3000
19
+ force_https = true
20
+ auto_stop_machines = true
21
+ auto_start_machines = true
22
+ min_machines_running = 1
23
+ processes = ["web"]
24
+ [http_service.concurrency]
25
+ type = "requests"
26
+ soft_limit = 200
27
+ hard_limit = 250
28
28
 
29
29
  [checks]
30
- [checks.alive]
31
- type = "tcp"
32
- interval = "15s"
33
- timeout = "2s"
34
- grace_period = "5s"
30
+ [checks.alive]
31
+ type = "tcp"
32
+ interval = "15s"
33
+ timeout = "2s"
34
+ grace_period = "5s"
35
35
 
36
36
  [[statics]]
37
- guest_path = "/plug-app/public"
38
- url_prefix = "/"
37
+ guest_path = "/plug-app/public"
38
+ url_prefix = "/"
39
+
40
+ [deploy]
41
+ strategy = "bluegreen"
@@ -2,32 +2,35 @@ app = "plug-app-staging"
2
2
  primary_region = "iad"
3
3
 
4
4
  [env]
5
- RAILS_ENV = "plug-app-staging"
6
- LD_PRELOAD_PATH = "/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
5
+ RAILS_ENV = "plug-app-staging"
6
+ LD_PRELOAD_PATH = "/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
7
7
 
8
8
  [processes]
9
- web = "./bin/rails server"
10
- worker = "bundle exec sidekiq"
9
+ web = "./bin/rails server"
10
+ worker = "bundle exec sidekiq"
11
11
 
12
12
  [http_service]
13
- internal_port = 3000
14
- force_https = true
15
- auto_stop_machines = true
16
- auto_start_machines = true
17
- min_machines_running = 0
18
- processes = ["web"]
19
- [http_service.concurrency]
20
- type = "requests"
21
- soft_limit = 200
22
- hard_limit = 250
13
+ internal_port = 3000
14
+ force_https = true
15
+ auto_stop_machines = true
16
+ auto_start_machines = true
17
+ min_machines_running = 0
18
+ processes = ["web"]
19
+ [http_service.concurrency]
20
+ type = "requests"
21
+ soft_limit = 200
22
+ hard_limit = 250
23
23
 
24
24
  [checks]
25
- [checks.alive]
26
- type = "tcp"
27
- interval = "15s"
28
- timeout = "2s"
29
- grace_period = "5s"
25
+ [checks.alive]
26
+ type = "tcp"
27
+ interval = "15s"
28
+ timeout = "2s"
29
+ grace_period = "5s"
30
30
 
31
31
  [[statics]]
32
- guest_path = "/plug-app/public"
33
- url_prefix = "/"
32
+ guest_path = "/plug-app/public"
33
+ url_prefix = "/"
34
+
35
+ [deploy]
36
+ strategy = "immediate"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hephaestus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garen Torikian
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-18 00:00:00.000000000 Z
11
+ date: 2023-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -171,6 +171,7 @@ files:
171
171
  - templates/app/lib/plug_app/middleware/not_found.rb
172
172
  - templates/app/lib/plug_app/middleware/openapi_validation.rb
173
173
  - templates/app/lib/plug_app/middleware/tracing_attributes.rb
174
+ - templates/app/lib/plug_app/switches/message_created.jsonc
174
175
  - templates/app/lib/query_parameter.rb
175
176
  - templates/app/serializers/error_serializer.rb
176
177
  - templates/app/services/http_service.rb
@@ -178,6 +179,7 @@ files:
178
179
  - templates/app/views/settings/new.json.jbuilder
179
180
  - templates/bin/docker-entrypoint
180
181
  - templates/compose.yml
182
+ - templates/config/initializers/000-oj.rb
181
183
  - templates/config/initializers/cors.rb
182
184
  - templates/config/initializers/environment.rb
183
185
  - templates/config/initializers/filter_parameter_logging.rb
@@ -197,8 +199,8 @@ files:
197
199
  - templates/lib/plug_app/schemas/api/2023-03-06/components/schemas/yetto.json
198
200
  - templates/lib/plug_app/schemas/api/2023-03-06/openapi.json
199
201
  - templates/lib/plug_app/schemas/api/2023-03-06/paths/plug.json
200
- - templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/after_create_message.json
201
- - templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/after_create_plug_installation.json
202
+ - templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/message_created.json
203
+ - templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/plug_installation_created.json
202
204
  - templates/lib/tasks/test_tasks.rake
203
205
  - templates/script/ci
204
206
  - templates/script/edit-credentials
@@ -214,6 +216,10 @@ files:
214
216
  - templates/test/controllers/settings_controller_test.rb
215
217
  - templates/test/controllers/yetto_controller_test.rb
216
218
  - templates/test/fixtures/files/fake_pem_file/fake.pem
219
+ - templates/test/fixtures/files/plug_installation_settings/invalid.json
220
+ - templates/test/fixtures/files/plug_installation_settings/valid.json
221
+ - templates/test/fixtures/plug_installation_settings/invalid.json
222
+ - templates/test/fixtures/plug_installation_settings/valid.json
217
223
  - templates/test/integration/.keep
218
224
  - templates/test/jobs/update_yetto_job_test.rb
219
225
  - templates/test/mailers/.keep
@@ -245,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
245
251
  - !ruby/object:Gem::Version
246
252
  version: 3.4.7
247
253
  requirements: []
248
- rubygems_version: 3.4.19
254
+ rubygems_version: 3.4.20
249
255
  signing_key:
250
256
  specification_version: 4
251
257
  summary: Generate a Rails app that can be used to create plugs for Yetto.