hephaestus 0.1.3 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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: