hephaestus 0.1.3 → 0.2.2

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +27 -1
  4. data/bin/hephaestus +9 -3
  5. data/lib/hephaestus/app_builder.rb +26 -5
  6. data/lib/hephaestus/exit_on_failure.rb +4 -4
  7. data/lib/hephaestus/generators/app_generator.rb +148 -56
  8. data/lib/hephaestus/generators/config_generator.rb +30 -2
  9. data/lib/hephaestus/generators/core_generator.rb +30 -1
  10. data/lib/hephaestus/generators/deployment_generator.rb +9 -3
  11. data/lib/hephaestus/generators/lib_generator.rb +1 -0
  12. data/lib/hephaestus/generators/sorbet_generator.rb +1 -1
  13. data/lib/hephaestus/version.rb +1 -1
  14. data/lib/hephaestus.rb +8 -0
  15. data/templates/.dockerignore +39 -0
  16. data/templates/.env.sample +6 -0
  17. data/templates/.github/dependabot.yml +12 -7
  18. data/templates/.github/workflows/automerge.yml +5 -85
  19. data/templates/.github/workflows/deploy.yml +30 -0
  20. data/templates/.github/workflows/licenses.yml +11 -31
  21. data/templates/.github/workflows/lint.yml +14 -34
  22. data/templates/.github/workflows/security.yml +9 -32
  23. data/templates/.github/workflows/sorbet.yml +7 -37
  24. data/templates/.github/workflows/test.yml +7 -41
  25. data/templates/.licensed.yml +7 -3
  26. data/templates/.ruby-version +1 -0
  27. data/templates/Dockerfile +79 -0
  28. data/templates/Gemfile.erb +23 -26
  29. data/templates/Procfile.debug +1 -1
  30. data/templates/app/controllers/app_controller.rb +71 -0
  31. data/templates/app/controllers/application_controller.rb +14 -1
  32. data/templates/app/controllers/concerns/authable.rb +20 -2
  33. data/templates/app/controllers/settings_controller.rb +32 -2
  34. data/templates/app/jobs/update_yetto_job.rb +6 -7
  35. data/templates/app/lib/body_parameter/yetto_parameters.rb +32 -0
  36. data/templates/app/lib/path_parameter/settings_parameters.rb +22 -0
  37. data/templates/app/lib/plug_app/middleware/openapi_validation.rb +5 -5
  38. data/templates/app/lib/plug_app/middleware/tracing_attributes.rb +1 -1
  39. data/templates/app/services/http_service.rb +27 -0
  40. data/templates/app/services/yetto_service.rb +24 -32
  41. data/templates/app/views/settings/new.json.jbuilder +21 -0
  42. data/templates/bin/docker-entrypoint +14 -0
  43. data/templates/compose.yml +7 -0
  44. data/templates/config/initializers/environment.rb +5 -8
  45. data/templates/config/initializers/filter_parameter_logging.rb +3 -0
  46. data/templates/config/initializers/opentelemetry.rb +32 -0
  47. data/templates/config/locales/en.yml +32 -0
  48. data/templates/config/locales/settings/en.yml +5 -0
  49. data/templates/lib/plug_app/schemas/api/2023-03-06/components/schemas/yetto.json +2 -2
  50. data/templates/lib/plug_app/schemas/api/2023-03-06/openapi.json +4 -4
  51. data/templates/lib/plug_app/schemas/api/2023-03-06/paths/plug.json +1 -1
  52. data/templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/after_create_message.json +2 -2
  53. data/templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/after_create_plug_installation.json +2 -2
  54. data/templates/lib/tasks/test_tasks.rake +6 -2
  55. data/templates/script/edit-credentials +29 -0
  56. data/templates/script/hmac_text +1 -1
  57. data/templates/script/licenses +4 -48
  58. data/templates/script/server +62 -2
  59. data/templates/script/sorbet +7 -0
  60. data/templates/test/controllers/application_controller_test.rb +32 -0
  61. data/templates/test/controllers/settings_controller_test.rb +1 -1
  62. data/templates/test/controllers/yetto_controller_test.rb +1 -1
  63. data/templates/test/fixtures/files/fake_pem_file/fake.pem +27 -0
  64. data/templates/test/jobs/update_yetto_job_test.rb +3 -18
  65. data/templates/test/support/api.rb +1 -1
  66. data/templates/test/support/rails.rb +1 -1
  67. data/templates/test/support/webmocks/slack_webmock.rb +2 -2
  68. data/templates/test/support/webmocks/yetto_webmock.rb +119 -0
  69. data/templates/test/test_helper.rb +16 -3
  70. data/templates/vendor/fly/fly-production.toml +38 -0
  71. data/templates/vendor/fly/fly-staging.toml +33 -0
  72. metadata +39 -16
  73. data/templates/.env.test +0 -4
  74. data/templates/.github/actions/license/action.yml +0 -11
  75. data/templates/.github/actions/setup/action.yml +0 -10
  76. data/templates/.github/actions/sisyphus/action.yml +0 -11
  77. data/templates/.github/actions/sorbet/action.yml +0 -19
  78. data/templates/app/views/settings/index.json.jbuilder +0 -15
  79. data/templates/config/initializers/open_telemetry.rb +0 -27
  80. data/templates/script/security_checks/brakeman +0 -5
  81. data/templates/script/security_checks/bundle-audit +0 -5
  82. data/templates/script/server-debug +0 -5
  83. data/templates/script/typecheck +0 -44
  84. data/templates/test/fixtures/files/.keep +0 -0
  85. data/templates/test/support/webmocks/yetto.rb +0 -94
@@ -1,20 +1,18 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) do |repo_name|
4
- repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5
- "https://github.com/#{repo_name}.git"
6
- end
3
+ source "https://rubygems.org"
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
7
5
 
8
- ruby "<%= Hephaestus::RUBY_VERSION %>"
6
+ ruby File.read(".ruby-version").strip
9
7
 
10
8
  # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
11
9
  gem "rails", "~> 7.0"
12
10
 
13
11
  # Use the Puma web server [https://github.com/puma/puma]
14
- gem "puma", "~> 6.1"
12
+ gem "puma", "~> 6.3"
15
13
 
16
14
  # for making kick-ass http queries
17
- gem "httpx", "~> 0.22"
15
+ gem "httpsensible", "~> 0.1"
18
16
 
19
17
  # Build JSON APIs with ease [https://github.com/rails/jbuilder]
20
18
  gem "jbuilder", "~> 2.11"
@@ -32,18 +30,18 @@ gem "lograge", "~> 0.12"
32
30
  gem "openapi_first", "~> 0.20"
33
31
 
34
32
  # For Honeycomb.io
35
- gem "opentelemetry-sdk", "~> 1.1"
36
- gem "opentelemetry-exporter-otlp", "~> 0.23"
37
- gem "opentelemetry-semantic_conventions", "~> 1.1"
33
+ gem "opentelemetry-sdk", "~> 1.2"
34
+ gem "opentelemetry-exporter-otlp", "~> 0.25"
35
+ gem "opentelemetry-semantic_conventions", "~> 1.10"
38
36
 
39
- gem "opentelemetry-instrumentation-rack", "~> 0.22"
40
- gem "opentelemetry-instrumentation-rails", "~> 0.24"
41
- gem "opentelemetry-instrumentation-concurrent_ruby", "~> 0.20"
37
+ gem "opentelemetry-instrumentation-rack", "~> 0.23"
38
+ gem "opentelemetry-instrumentation-rails", "~> 0.27"
39
+ gem "opentelemetry-instrumentation-concurrent_ruby", "~> 0.21"
42
40
 
43
- gem "opentelemetry-instrumentation-net_http", "~> 0.21"
41
+ gem "opentelemetry-instrumentation-net_http", "~> 0.22"
44
42
 
45
- gem "opentelemetry-instrumentation-active_job", "~> 0.4"
46
- gem "opentelemetry-instrumentation-redis", "~> 0.24"
43
+ gem "opentelemetry-instrumentation-active_job", "~> 0.5"
44
+ gem "opentelemetry-instrumentation-redis", "~> 0.25"
47
45
  gem "opentelemetry-instrumentation-sidekiq", "~> 0.23"
48
46
 
49
47
  # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
@@ -55,10 +53,10 @@ gem "bootsnap", require: false
55
53
  gem "safety_dance", "~> 1.0"
56
54
 
57
55
  # Use Sidekiq for the jobs queue
58
- gem "sidekiq", "~> 7.0"
56
+ gem "sidekiq", "~> 7.1"
59
57
 
60
58
  # sends logs to Slack
61
- gem "slack_webhook_logger", "~> 0.1"
59
+ gem "slack_webhook_logger", "~> 0.5"
62
60
 
63
61
  group :development, :test do
64
62
  # better debug output with `ap`
@@ -73,13 +71,15 @@ group :development, :test do
73
71
  end
74
72
 
75
73
  group :development do
74
+ gem "dockerfile-rails", "~> 1.5"
75
+
76
76
  gem "dotenv-rails"
77
77
 
78
78
  gem "foreman", "~> 0.87"
79
79
 
80
- gem "licensed", "~> 4.0"
80
+ gem "licensed", "~> 4.4"
81
81
 
82
- gem "ruby-lsp", "~> 0.3", require: false
82
+ gem "ruby-lsp", "~> 0.6", require: false
83
83
 
84
84
  gem "spoom"
85
85
  gem "sorbet"
@@ -88,8 +88,8 @@ end
88
88
  gem "sorbet-runtime"
89
89
 
90
90
  group :test do
91
- # Adds support for Capybara system testing and selenium driver
92
- gem "capybara", "~> 3.26"
91
+ gem "simplecov", "~> 0.18", require: false
92
+ gem "simplecov-console", "~> 0.7", require: false
93
93
 
94
94
  # track down flakey tests
95
95
  gem "minitest-bisect"
@@ -106,9 +106,6 @@ group :test do
106
106
  # jump around through time
107
107
  gem "timecop", "~> 0.9"
108
108
 
109
- # Easy installation and use of web drivers to run system tests with browsers
110
- gem "webdrivers", "~> 5.0", require: false
111
-
112
109
  # prevents real http requests
113
110
  gem "webmock", "~> 3.8"
114
111
  end
@@ -1,2 +1,2 @@
1
- web: rdbg -n --open=vscode -c -- bin/rails server -p 6661
1
+ web: rdbg -n -O -c -- bin/rails server -p 6661
2
2
  worker: bundle exec sidekiq -c 2
@@ -0,0 +1,71 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ class AppController < ApplicationController
5
+ include Authable
6
+
7
+ include Constants::PlugApp
8
+ include PathParameter::AppParameters
9
+ include BodyParameter::AppParameters
10
+
11
+ before_action :from__app_?
12
+
13
+ # Inbound message from ${App}
14
+ def webhook
15
+ # Error if necesary parameters from ${App} are missing
16
+ return bad_request unless has_inbound__app__params?
17
+
18
+ response = YettoService.get_plug_installation(pparam_organization_id, pparam_inbox_id, pparam_plug_installation_id)
19
+
20
+ # Error if Yetto is down
21
+ return service_unavailable(response) if response.unavailable?
22
+
23
+ plug_installation = response.parse_json_body
24
+ installed_on_inbox = plug_installation.fetch("installed_on_inbox", {})
25
+
26
+ organization = installed_on_inbox.fetch("organization", {})
27
+
28
+ # Bail if the organization is not active
29
+ return forbidden unless organization.fetch("status", "") == "active"
30
+
31
+ plug_id = plug_installation.fetch("plug", {}).fetch("id", "")
32
+ inbox_id = installed_on_inbox.fetch("id", "")
33
+ organization_id = organization.fetch("id", "")
34
+
35
+ return bad_request if plug_id.blank? || inbox_id.blank?
36
+
37
+ # Send the message to Yetto
38
+ update_data = {
39
+ type: "create_message",
40
+ inbox: { id: inbox_id },
41
+ organization: { id: organization_id },
42
+ payload: {
43
+ creator: {
44
+ id: plug_id,
45
+ },
46
+ message: {
47
+ title: title,
48
+ text_content: text_body,
49
+ is_public: true,
50
+ author: {
51
+ name: from_email,
52
+ },
53
+ attachments: bparam_attachments,
54
+ metadata: {
55
+ cc_addresses: cc_addresses,
56
+ postmark_message_id: bparam_message_id,
57
+ email_message_id: email_message_id,
58
+ },
59
+ },
60
+ },
61
+ }
62
+
63
+ UpdateYettoJob.perform_later(update_data)
64
+
65
+ created
66
+ end
67
+
68
+ def process_inbound
69
+ no_content
70
+ end
71
+ end
@@ -65,7 +65,7 @@ class ApplicationController < ActionController::Base
65
65
  )
66
66
  end
67
67
 
68
- def not_acceptable(e)
68
+ def not_acceptable
69
69
  render(
70
70
  json: ::ErrorSerializer.format("Not Acceptable").to_json,
71
71
  status: :not_acceptable,
@@ -104,4 +104,17 @@ class ApplicationController < ActionController::Base
104
104
  status: :bad_gateway,
105
105
  )
106
106
  end
107
+
108
+ def internal_server_error
109
+ render(
110
+ json: {
111
+ errors: [
112
+ {
113
+ message: "Internal Server Error",
114
+ },
115
+ ],
116
+ }.to_json,
117
+ status: :internal_server_error,
118
+ )
119
+ end
107
120
  end
@@ -12,6 +12,24 @@ module Authable
12
12
 
13
13
  SHA256_DIGEST = OpenSSL::Digest.new("sha256")
14
14
 
15
+ sig { void }
16
+ def from__app_?
17
+ state = params.fetch(:state, "")
18
+ _, _, gh_nonce, _, _, _, _ = parse_state(state)
19
+
20
+ return if ActiveSupport::SecurityUtils.secure_compare((gh_nonce || ""), PLUG_APP_NONCE)
21
+
22
+ self.status = PlugApp::HTTP::BAD_REQUEST_I
23
+ self.response_body = ::ErrorSerializer.format(PlugApp::HTTP::BAD_REQUEST)
24
+
25
+ return true if response.status == 200
26
+
27
+ # status is annoyingly set to 401, but we want
28
+ # to hide that an issue exists
29
+ response.status = PlugApp::HTTP::BAD_REQUEST_I
30
+ response.body = ::ErrorSerializer.format(PlugApp::HTTP::BAD_REQUEST)
31
+ end
32
+
15
33
  sig { void }
16
34
  def from_yetto?
17
35
  return bad_request if request.headers.blank?
@@ -21,9 +39,9 @@ module Authable
21
39
  return bad_request unless yetto_signature.start_with?("sha256=")
22
40
 
23
41
  hmac_header = yetto_signature.split("sha256=").last
24
- body = request.env["RAW_POST_DATA"]
42
+ body = request.env.fetch("RAW_POST_DATA", "")
25
43
 
26
- calculated_hmac = OpenSSL::HMAC.hexdigest(SHA256_DIGEST, YETTO_PLUG_APP_TOKEN, body)
44
+ calculated_hmac = OpenSSL::HMAC.hexdigest(SHA256_DIGEST, SIGNING_SECRET, body)
27
45
 
28
46
  return true if ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
29
47
 
@@ -1,7 +1,37 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class SettingsController < ApplicationController
5
- def index
5
+ extend T::Sig
6
+
7
+ include PathParameter::SettingsParameters
8
+
9
+ sig { void }
10
+ def new
11
+ @step = T.let(params.fetch(:step, 1).to_i || 1, T.nilable(Integer))
12
+ end
13
+
14
+ sig { void }
15
+ def edit
16
+ @step = params.fetch(:step, 1).to_i || 1
17
+
18
+ response = YettoService.get_plug_installation(pparam_plug_installation_id)
19
+
20
+ if response.unavailable?
21
+ logger.error("Fetching Yetto inbox failed: `#{response}`")
22
+
23
+ return not_acceptable
24
+ end
25
+
26
+ plug_installation = response.parse_json_body
27
+ access_token = plug_installation.fetch("credentials", {}).fetch("access_token", "")
28
+
29
+ if access_token.blank?
30
+ logger.error("Fetching Yetto access_token failed: `#{response}`")
31
+
32
+ return not_acceptable
33
+ end
34
+
35
+ not_found
6
36
  end
7
37
  end
@@ -15,13 +15,12 @@ class UpdateYettoJob < ApplicationJob
15
15
  plug_installation_id = params.fetch(:plug_installation, {}).fetch(:id, nil)
16
16
 
17
17
  case type
18
- when "installation"
19
- YettoService.update_installation(organization_id, inbox_id, plug_installation_id, params)
20
- when "switch"
21
- plug_id = params.fetch(:plug, {}).fetch(:id, nil)
22
- YettoService.create_switch(organization_id, inbox_id, plug_id, params)
23
- when "message"
24
- YettoService.create_message(organization_id, inbox_id, params)
18
+ when "update_plug_installation"
19
+ YettoService.update_installation(plug_installation_id, params)
20
+ when "create_inbox_switch"
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)
25
24
  end
26
25
  end
27
26
  end
@@ -4,5 +4,37 @@
4
4
  module BodyParameter
5
5
  module YettoParameters
6
6
  extend T::Sig
7
+
8
+ sig { returns(T::Boolean) }
9
+ def has_update_plug_installation_params?
10
+ return false if update_plug_installation_params.blank?
11
+
12
+ true
13
+ end
14
+
15
+ sig { returns(T::Hash[Symbol, String]) }
16
+ def update_plug_installation_params
17
+ return {} if params.blank?
18
+
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?
23
+
24
+ {
25
+ plug_installation: {
26
+ id: plug_installation_id,
27
+ },
28
+ organization: {
29
+ id: organization_id,
30
+ },
31
+ plug: {
32
+ id: plug_id,
33
+ },
34
+ inbox: {
35
+ id: inbox_id,
36
+ },
37
+ }
38
+ end
7
39
  end
8
40
  end
@@ -0,0 +1,22 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module PathParameter
5
+ module SettingsParameters
6
+ extend T::Sig
7
+
8
+ sig { returns(String) }
9
+ def pparam_plug_installation_id
10
+ yetto_path_params.fetch(:event, "")
11
+ end
12
+
13
+ sig { returns(T::Hash[Symbol, T.untyped]) }
14
+ def settings_path_params
15
+ return {} if params.blank?
16
+
17
+ {
18
+ plug_installation_id: params.fetch(:plug_installation_id, ""),
19
+ }
20
+ end
21
+ end
22
+ end
@@ -8,12 +8,12 @@ module PlugApp
8
8
  class OpenapiValidation
9
9
  API_PATH_PREFIX = "/api/"
10
10
  SPEC_PATH = Rails.root.join("lib/plug_app/schemas/api/2023-03-06/openapi.json")
11
+ SPEC = OpenapiFirst.load(SPEC_PATH)
11
12
 
12
13
  def initialize(app)
13
14
  @app = app
14
- spec = OpenapiFirst.load(SPEC_PATH)
15
- @response_validator = OpenapiFirst::ResponseValidator.new(spec)
16
- @request_validator = OpenapiFirst::RequestValidation.new(->(_env) {}, spec: SPEC_PATH, raise_error: true)
15
+ @response_validator = OpenapiFirst::ResponseValidator.new(SPEC)
16
+ @request_validator = OpenapiFirst::RequestValidation.new(->(_env) {}, spec: SPEC, raise_error: true)
17
17
  end
18
18
 
19
19
  def call(env)
@@ -33,9 +33,9 @@ module PlugApp
33
33
  PlugApp::Middleware::NotFound.new.call(env)
34
34
  rescue OpenapiFirst::RequestInvalidError => e
35
35
  error_hsh = ::ErrorSerializer.format(e.message)
36
-
36
+ Rails.logger.error(error_hsh) if print_user_api_errors?
37
37
  return [PlugApp::HTTP::BAD_REQUEST_I, { "Content-Type" => "application/json" }, [error_hsh]]
38
- rescue => e
38
+ rescue StandardError => e
39
39
  raise e unless Rails.env.production?
40
40
 
41
41
  logger.error(
@@ -13,7 +13,7 @@ module PlugApp
13
13
  attr_reader :app
14
14
 
15
15
  HTTP_REQUEST_BODY = "http.request.body"
16
- PLUG_APP_PATH_PREFIX = "/${app}/"
16
+ PLUG_APP_PATH_PREFIX = "/app/"
17
17
 
18
18
  sig { params(app: T.untyped).void }
19
19
  def initialize(app)
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ class HTTPService
5
+ extend T::Sig
6
+
7
+ class << self
8
+ extend T::Sig
9
+
10
+ CACHE_VERSION = "1"
11
+
12
+ sig { returns(T.untyped) }
13
+ def http_client
14
+ Httpsensible::Client.new(user_agent: "PlugApp/#{PlugApp::Application::GIT_SHA}")
15
+ end
16
+
17
+ sig { params(url: String, params: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
18
+ def get(url, params: {})
19
+ http_client.get(url, params: params)
20
+ end
21
+
22
+ sig { params(url: String, params: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
23
+ def post(url, params: {})
24
+ http_client.post(url, params: params)
25
+ end
26
+ end
27
+ end
@@ -1,61 +1,53 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class YettoService
5
5
  cattr_reader :yetto_client, instance_accessor: false do
6
- HTTPX.plugin(:persistent).with_headers({
7
- "Content-Type" => "application/json",
8
- "Accept" => "application/json",
9
- "User-Agent" => "PlugApp/#{PlugApp::Application::GIT_SHA}",
10
- })
6
+ Httpsensible::Client.new(user_agent: "PlugApp/#{PlugApp::Application::GIT_SHA}")
11
7
  end
12
8
 
13
9
  PROTOCOL = Rails.env.development? ? "http" : "https"
14
10
  YETTO_API_VERSION_TLD = "#{PROTOCOL}://#{YETTO_API_TLD}/#{YETTO_API_VERSION}"
11
+ JWT_ALGORITHM = "RS256"
15
12
 
16
13
  class << self
17
- def get_plug_installation(organization_id, inbox_id, plug_installation_id)
18
- yetto_client.get("#{YETTO_API_VERSION_TLD}/organizations/#{organization_id}/inboxes/#{inbox_id}/plug_installations/#{plug_installation_id}")
14
+ def perform_token_exchange(plug_installation_id)
15
+ 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")
17
+ body = response.parsed_json_body
18
+ body["token"]
19
19
  end
20
20
 
21
- def update_installation(organization_id, inbox_id, plug_installation_id, params)
21
+ def get_plug_installation(plug_installation_id)
22
+ token = perform_token_exchange(plug_installation_id)
23
+ yetto_client.with(headers: { "Authorization" => "Bearer #{token}" }).get("#{YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
24
+ end
25
+
26
+ def update_installation(plug_installation_id, params)
22
27
  plug_installation = {}
23
28
  plug_installation[:settings] = params[:plug_installation].fetch(:settings, {})
24
29
  plug_installation[:credentials] = params[:plug_installation].fetch(:credentials, {})
25
- body = {
26
- plug_installation: plug_installation,
27
- }
28
30
 
29
- yetto_client.patch("#{YETTO_API_VERSION_TLD}/organizations/#{organization_id}/inboxes/#{inbox_id}/plug_installations/#{plug_installation_id}", json: body)
31
+ 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)
30
33
  end
31
34
 
32
- def create_switch(organization_id, inbox_id, plug_id, params)
35
+ def create_inbox_switch(inbox_id, plug_installation_id, params)
33
36
  inbox_id = params.fetch(:inbox).fetch(:id)
34
- creator_id = params.fetch(:plug).fetch(:id)
37
+
35
38
  payload = {
36
39
  name: "After install",
37
- creator: { id: creator_id },
38
40
  }
39
- yetto_client.post("#{YETTO_API_VERSION_TLD}/organizations/#{organization_id}/inboxes/#{inbox_id}/switches", json: payload)
40
- end
41
41
 
42
- def create_message(organization_id, inbox_id, params)
43
- payload = params[:payload]
44
- yetto_client.post("#{YETTO_API_VERSION_TLD}/organizations/#{organization_id}/inboxes/#{inbox_id}/messages", json: payload)
42
+ 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)
45
44
  end
46
45
 
47
- def parse_body(response)
48
- JSON.parse(response.body.read)
49
- end
50
-
51
- def unavailable?(response)
52
- return true unless (200..299).cover?(response.status)
53
-
54
- response.body.blank? || response.body == "{}"
55
- end
46
+ def create_message(inbox_id, plug_installation_id, params)
47
+ payload = params[:payload]
56
48
 
57
- def available?(response)
58
- !unavailable?(response)
49
+ 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)
59
51
  end
60
52
  end
61
53
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.version("2023-03-06")
4
+ json.settings do
5
+ json.array!([
6
+ {
7
+ component: "text_field",
8
+ name: "from_name",
9
+ heading: t(:some_example_heading),
10
+ placeholder: "{{ organization['name'] }} - {{ inbox['name'] }}",
11
+ label: t(:some_example),
12
+ },
13
+ {
14
+ component: "email_field",
15
+ name: "reply_to_email",
16
+ heading: t(:another_example_heading),
17
+ placeholder: "{{ inbox['slug'] }}@{{ organization['name'] | downcase }}.biz",
18
+ label: t(:another_example_label),
19
+ },
20
+ ])
21
+ end
@@ -0,0 +1,14 @@
1
+ #!/bin/bash -e
2
+
3
+ # Add any container initialization steps here
4
+ git config --global --add safe.directory /plug-app
5
+
6
+ # allocate swap space
7
+ fallocate -l 2GB /swapfile
8
+ chmod 0600 /swapfile
9
+ mkswap /swapfile
10
+ echo 10 > /proc/sys/vm/swappiness
11
+ swapon /swapfile
12
+ echo 1 > /proc/sys/vm/overcommit_memory
13
+
14
+ exec "${@}"
@@ -0,0 +1,7 @@
1
+ version: "3.8"
2
+
3
+ services:
4
+ redis-db:
5
+ image: redis:7-bookworm
6
+ ports:
7
+ - "6389:6379"
@@ -14,16 +14,13 @@ elsif Rails.env.test?
14
14
  end
15
15
 
16
16
  YETTO_API_TLD = ENV.fetch("YETTO_API_TLD", "#{YETTO_URL}/api")
17
- YETTO_API_VERSION = ENV.fetch("YETTO_API_VERSION", "v1")
17
+ YETTO_API_VERSION = ENV.fetch("YETTO_API_VERSION", "2023-03-06")
18
18
 
19
- SLACK_LOG_URL = ENV.fetch("SLACK_LOG_URL", nil)
19
+ SLACK_LOG_URL = Rails.application.credentials.fetch(:SLACK_LOG_URL, ENV.fetch("SLACK_LOG_URL", "https://slack.com/the_log_room"))
20
20
 
21
- YETTO_PLUG_APP_TOKEN = ENV.fetch("YETTO_PLUG_APP_TOKEN", "super-secret")
22
-
23
- # For Honeycomb.io
24
- OTEL_EXPORTER_OTLP_ENDPOINT = ENV.fetch("OTEL_EXPORTER_OTLP_ENDPOINT", "https://api.honeycomb.io")
25
- OTEL_EXPORTER_OTLP_HEADERS = ENV.fetch("OTEL_EXPORTER_OTLP_HEADERS", "x-honeycomb-team=your-api-key")
26
- OTEL_SERVICE_NAME = ENV.fetch("OTEL_SERVICE_NAME", "your-service-name")
21
+ YETTO_PLUG_PEM = Rails.application.credentials.fetch(:YETTO_PLUG_PEM, ENV.fetch("YETTO_PLUG_PEM", Rails.root.join("test/fixtures/files/fake_pem_file/fake.pem").read))
22
+ SIGNING_SECRET = Rails.application.credentials.fetch(:PLUG_EMAIL_URL, ENV.fetch("PLUG_EMAIL_URL", "super-secret"))
23
+ YETTO_PLUG_ID = Rails.application.credentials.fetch(:YETTO_PLUG_ID, ENV.fetch("YETTO_PLUG_ID", "super-secret"))
27
24
 
28
25
  def productionish?
29
26
  Rails.env.production? || Rails.env.staging?
@@ -17,6 +17,9 @@ Rails.application.config.filter_parameters += [
17
17
  :otp,
18
18
  :ssn,
19
19
  :credentials,
20
+ :state,
21
+ :access_token,
22
+ :refresh_token,
20
23
  :html_content,
21
24
  :text_content,
22
25
  ]
@@ -0,0 +1,32 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ unless Rails.env.development?
5
+ # establish the environment for OTEL
6
+ ENV["OTEL_EXPORTER_OTLP_ENDPOINT"] = Rails.application.credentials.fetch(:OTEL_EXPORTER_OTLP_ENDPOINT, ENV.fetch("OTEL_EXPORTER_OTLP_ENDPOINT", "https://api.honeycomb.io"))
7
+ ENV["OTEL_EXPORTER_OTLP_HEADERS"] = Rails.application.credentials.fetch(:OTEL_EXPORTER_OTLP_HEADERS, ENV.fetch("OTEL_EXPORTER_OTLP_HEADERS", "x-honeycomb-team=your-api-key"))
8
+ ENV["OTEL_SERVICE_NAME"] = Rails.application.credentials.fetch(:OTEL_SERVICE_NAME, ENV.fetch("OTEL_SERVICE_NAME", "your-service-name"))
9
+
10
+ require "opentelemetry/sdk"
11
+ require "opentelemetry/semantic_conventions"
12
+
13
+ OpenTelemetry::SDK.configure do |c|
14
+ c.logger = Rails.logger
15
+
16
+ c.use_all("OpenTelemetry::Instrumentation::PG" => { db_statement: :obfuscate })
17
+
18
+ if productionish?
19
+ c.add_span_processor(
20
+ OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
21
+ OpenTelemetry::Exporter::OTLP::Exporter.new,
22
+ ),
23
+ )
24
+ else # useful for testing instrumentation
25
+ c.add_span_processor(
26
+ OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
27
+ OpenTelemetry::SDK::Trace::Export::SpanExporter.new,
28
+ ),
29
+ )
30
+ end # development is intentionally disabled
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # Files in the config/locales directory are used for internationalization
2
+ # and are automatically loaded by Rails. If you want to use locales other
3
+ # than English, add the necessary files in this directory.
4
+ #
5
+ # To use the locales, use `I18n.t`:
6
+ #
7
+ # I18n.t "hello"
8
+ #
9
+ # In views, this is aliased to just `t`:
10
+ #
11
+ # <%= t("hello") %>
12
+ #
13
+ # To use a different locale, set it with `I18n.locale`:
14
+ #
15
+ # I18n.locale = :es
16
+ #
17
+ # This would use the information in config/locales/es.yml.
18
+ #
19
+ # The following keys must be escaped otherwise they will not be retrieved by
20
+ # the default I18n backend:
21
+ #
22
+ # true, false, on, off, yes, no
23
+ #
24
+ # Instead, surround them with single quotes.
25
+ #
26
+ # en:
27
+ # "true": "foo"
28
+ #
29
+ # To learn more, please read the Rails Internationalization guide
30
+ # available at https://guides.rubyonrails.org/i18n.html.
31
+
32
+ en: