hephaestus 0.1.3 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +27 -1
- data/bin/hephaestus +9 -3
- data/lib/hephaestus/app_builder.rb +26 -5
- data/lib/hephaestus/exit_on_failure.rb +4 -4
- data/lib/hephaestus/generators/app_generator.rb +148 -56
- data/lib/hephaestus/generators/config_generator.rb +30 -2
- data/lib/hephaestus/generators/core_generator.rb +30 -1
- data/lib/hephaestus/generators/deployment_generator.rb +9 -3
- data/lib/hephaestus/generators/lib_generator.rb +1 -0
- data/lib/hephaestus/generators/sorbet_generator.rb +1 -1
- data/lib/hephaestus/version.rb +1 -1
- data/lib/hephaestus.rb +8 -0
- data/templates/.dockerignore +39 -0
- data/templates/.env.sample +6 -0
- data/templates/.github/dependabot.yml +12 -7
- data/templates/.github/workflows/automerge.yml +5 -85
- data/templates/.github/workflows/deploy.yml +30 -0
- data/templates/.github/workflows/licenses.yml +11 -31
- data/templates/.github/workflows/lint.yml +14 -34
- data/templates/.github/workflows/security.yml +9 -32
- data/templates/.github/workflows/sorbet.yml +7 -37
- data/templates/.github/workflows/test.yml +7 -41
- data/templates/.licensed.yml +7 -3
- data/templates/.ruby-version +1 -0
- data/templates/Dockerfile +79 -0
- data/templates/Gemfile.erb +23 -26
- data/templates/Procfile.debug +1 -1
- data/templates/app/controllers/app_controller.rb +71 -0
- data/templates/app/controllers/application_controller.rb +14 -1
- data/templates/app/controllers/concerns/authable.rb +20 -2
- data/templates/app/controllers/settings_controller.rb +32 -2
- data/templates/app/jobs/update_yetto_job.rb +6 -7
- data/templates/app/lib/body_parameter/yetto_parameters.rb +32 -0
- data/templates/app/lib/path_parameter/settings_parameters.rb +22 -0
- data/templates/app/lib/plug_app/middleware/openapi_validation.rb +5 -5
- data/templates/app/lib/plug_app/middleware/tracing_attributes.rb +1 -1
- data/templates/app/services/http_service.rb +27 -0
- data/templates/app/services/yetto_service.rb +24 -32
- data/templates/app/views/settings/new.json.jbuilder +21 -0
- data/templates/bin/docker-entrypoint +14 -0
- data/templates/compose.yml +7 -0
- data/templates/config/initializers/environment.rb +5 -8
- data/templates/config/initializers/filter_parameter_logging.rb +3 -0
- data/templates/config/initializers/opentelemetry.rb +32 -0
- data/templates/config/locales/en.yml +32 -0
- data/templates/config/locales/settings/en.yml +5 -0
- data/templates/lib/plug_app/schemas/api/2023-03-06/components/schemas/yetto.json +2 -2
- data/templates/lib/plug_app/schemas/api/2023-03-06/openapi.json +4 -4
- data/templates/lib/plug_app/schemas/api/2023-03-06/paths/plug.json +1 -1
- data/templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/after_create_message.json +2 -2
- data/templates/lib/plug_app/schemas/api/2023-03-06/paths/yetto/after_create_plug_installation.json +2 -2
- data/templates/lib/tasks/test_tasks.rake +6 -2
- data/templates/script/edit-credentials +29 -0
- data/templates/script/hmac_text +1 -1
- data/templates/script/licenses +4 -48
- data/templates/script/server +62 -2
- data/templates/script/sorbet +7 -0
- data/templates/test/controllers/application_controller_test.rb +32 -0
- data/templates/test/controllers/settings_controller_test.rb +1 -1
- data/templates/test/controllers/yetto_controller_test.rb +1 -1
- data/templates/test/fixtures/files/fake_pem_file/fake.pem +27 -0
- data/templates/test/jobs/update_yetto_job_test.rb +3 -18
- data/templates/test/support/api.rb +1 -1
- data/templates/test/support/rails.rb +1 -1
- data/templates/test/support/webmocks/slack_webmock.rb +2 -2
- data/templates/test/support/webmocks/yetto_webmock.rb +119 -0
- data/templates/test/test_helper.rb +16 -3
- data/templates/vendor/fly/fly-production.toml +38 -0
- data/templates/vendor/fly/fly-staging.toml +33 -0
- metadata +39 -16
- data/templates/.env.test +0 -4
- data/templates/.github/actions/license/action.yml +0 -11
- data/templates/.github/actions/setup/action.yml +0 -10
- data/templates/.github/actions/sisyphus/action.yml +0 -11
- data/templates/.github/actions/sorbet/action.yml +0 -19
- data/templates/app/views/settings/index.json.jbuilder +0 -15
- data/templates/config/initializers/open_telemetry.rb +0 -27
- data/templates/script/security_checks/brakeman +0 -5
- data/templates/script/security_checks/bundle-audit +0 -5
- data/templates/script/server-debug +0 -5
- data/templates/script/typecheck +0 -44
- data/templates/test/fixtures/files/.keep +0 -0
- data/templates/test/support/webmocks/yetto.rb +0 -94
data/templates/Gemfile.erb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
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 "
|
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.
|
12
|
+
gem "puma", "~> 6.3"
|
15
13
|
|
16
14
|
# for making kick-ass http queries
|
17
|
-
gem "
|
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.
|
36
|
-
gem "opentelemetry-exporter-otlp", "~> 0.
|
37
|
-
gem "opentelemetry-semantic_conventions", "~> 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.
|
40
|
-
gem "opentelemetry-instrumentation-rails", "~> 0.
|
41
|
-
gem "opentelemetry-instrumentation-concurrent_ruby", "~> 0.
|
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.
|
41
|
+
gem "opentelemetry-instrumentation-net_http", "~> 0.22"
|
44
42
|
|
45
|
-
gem "opentelemetry-instrumentation-active_job", "~> 0.
|
46
|
-
gem "opentelemetry-instrumentation-redis", "~> 0.
|
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.
|
56
|
+
gem "sidekiq", "~> 7.1"
|
59
57
|
|
60
58
|
# sends logs to Slack
|
61
|
-
gem "slack_webhook_logger", "~> 0.
|
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.
|
80
|
+
gem "licensed", "~> 4.4"
|
81
81
|
|
82
|
-
gem "ruby-lsp", "~> 0.
|
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
|
-
|
92
|
-
gem "
|
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
|
data/templates/Procfile.debug
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
web: rdbg -n
|
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
|
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
|
42
|
+
body = request.env.fetch("RAW_POST_DATA", "")
|
25
43
|
|
26
|
-
calculated_hmac = OpenSSL::HMAC.hexdigest(SHA256_DIGEST,
|
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:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
class SettingsController < ApplicationController
|
5
|
-
|
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 "
|
19
|
-
YettoService.update_installation(
|
20
|
-
when "
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
15
|
-
@
|
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(
|
@@ -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:
|
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
|
-
|
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
|
18
|
-
|
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
|
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
|
-
|
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
|
35
|
+
def create_inbox_switch(inbox_id, plug_installation_id, params)
|
33
36
|
inbox_id = params.fetch(:inbox).fetch(:id)
|
34
|
-
|
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
|
-
|
43
|
-
|
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
|
48
|
-
|
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
|
-
|
58
|
-
|
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 "${@}"
|
@@ -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", "
|
17
|
+
YETTO_API_VERSION = ENV.fetch("YETTO_API_VERSION", "2023-03-06")
|
18
18
|
|
19
|
-
SLACK_LOG_URL = ENV.fetch("SLACK_LOG_URL",
|
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
|
-
|
22
|
-
|
23
|
-
|
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?
|
@@ -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:
|