hephaestus 0.3.1 → 0.4.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 (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.