katalyst-google-apis 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/controllers/recaptcha_controller.js +40 -19
- data/app/services/katalyst/google_apis/error.rb +20 -0
- data/app/services/katalyst/google_apis/gemini/generate_content_service.rb +95 -0
- data/app/services/katalyst/google_apis/recaptcha/assessment_service.rb +32 -16
- data/config/ci.rb +15 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9811888c443080f2c6e2107db38c63738cfebe5bbcb912779792924401301db1
|
|
4
|
+
data.tar.gz: c247d6f9e6587a57d7375766e3db44f20c6278236016a44eab54a988f3449a23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1db1c91469d9628497630629ea4a4d2fa96da7051fc5716bd59a661f01c2fbad7c4ace74094f0e46d880bcf3e2762e0c8dc904098d5cdf151fc90a647b5f7cf4
|
|
7
|
+
data.tar.gz: 0d6829ce0f4164179d7b2b6f1d871b9eba78498e7cf9ccb67f9a362d3e1ab3617bd2d5c7848427234e654e03786191b029b0749b24dc11055cad27c6252c1b98
|
|
@@ -10,25 +10,6 @@ enterprise.ready = (f) => {
|
|
|
10
10
|
(config["fns"] ||= []).push(f);
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Inject recaptcha enterprise script into the head, unless it is already there.
|
|
15
|
-
*/
|
|
16
|
-
function injectRecaptchaScripts() {
|
|
17
|
-
if (document.head.querySelector("script[src*='recaptcha/enterprise']")) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const script = document.createElement("script");
|
|
22
|
-
script.setAttribute(
|
|
23
|
-
"src",
|
|
24
|
-
"https://www.google.com/recaptcha/enterprise.js?render=explicit",
|
|
25
|
-
);
|
|
26
|
-
script.toggleAttribute("async", true);
|
|
27
|
-
script.toggleAttribute("defer", true);
|
|
28
|
-
|
|
29
|
-
document.head.appendChild(script);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
13
|
export default class RecaptchaController extends Controller {
|
|
33
14
|
static values = {
|
|
34
15
|
siteKey: String,
|
|
@@ -122,3 +103,43 @@ export default class RecaptchaController extends Controller {
|
|
|
122
103
|
return this.element.nextElementSibling;
|
|
123
104
|
}
|
|
124
105
|
}
|
|
106
|
+
|
|
107
|
+
function getMetaElement(name) {
|
|
108
|
+
return document.querySelector(`meta[name="${name}"]`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getCspNonce() {
|
|
112
|
+
const element = getMetaElement("csp-nonce");
|
|
113
|
+
if (element) {
|
|
114
|
+
const { nonce: nonce, content: content } = element;
|
|
115
|
+
return nonce === "" ? content : nonce;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createRecaptchaScriptElement() {
|
|
120
|
+
const element = document.createElement("script");
|
|
121
|
+
element.src =
|
|
122
|
+
"https://www.google.com/recaptcha/enterprise.js?render=explicit";
|
|
123
|
+
const cspNonce = getCspNonce();
|
|
124
|
+
if (cspNonce) {
|
|
125
|
+
element.setAttribute("nonce", cspNonce);
|
|
126
|
+
}
|
|
127
|
+
element.async = true;
|
|
128
|
+
element.defer = true;
|
|
129
|
+
|
|
130
|
+
return element;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Inject recaptcha enterprise script into the head, unless it is already there.
|
|
135
|
+
*/
|
|
136
|
+
function injectRecaptchaScripts() {
|
|
137
|
+
let element = document.head.querySelector(
|
|
138
|
+
"script[src*='recaptcha/enterprise']",
|
|
139
|
+
);
|
|
140
|
+
if (!element) {
|
|
141
|
+
element = createRecaptchaScriptElement();
|
|
142
|
+
document.head.appendChild(element);
|
|
143
|
+
}
|
|
144
|
+
return element;
|
|
145
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Katalyst
|
|
4
|
+
module GoogleApis
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :code, :status
|
|
7
|
+
|
|
8
|
+
def initialize(code:, status:, message:)
|
|
9
|
+
super(message)
|
|
10
|
+
|
|
11
|
+
@code = code
|
|
12
|
+
@status = status
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def inspect
|
|
16
|
+
"#<#{self.class.name} code=#{code.inspect} status=#{status.inspect} message=#{message.inspect}>"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Katalyst
|
|
4
|
+
module GoogleApis
|
|
5
|
+
module Gemini
|
|
6
|
+
# Use Gemini GPT to generate a response to a prompt.
|
|
7
|
+
class GenerateContentService
|
|
8
|
+
attr_reader :response, :result, :error, :content_text
|
|
9
|
+
|
|
10
|
+
def self.call(parent:, model:, payload:, credentials: Katalyst::GoogleApis.credentials)
|
|
11
|
+
new(parent:, model:, credentials:).call(payload:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(credentials:, model:, parent:)
|
|
15
|
+
@credentials = credentials
|
|
16
|
+
@model = model
|
|
17
|
+
@parent = parent
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(payload:)
|
|
21
|
+
@response = Curl.post(url, payload.to_json) do |http|
|
|
22
|
+
http.headers["Content-Type"] = "application/json; UTF-8"
|
|
23
|
+
@credentials.apply!(http.headers)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if @response.content_type == "application/json"
|
|
27
|
+
@result = JSON.parse(response.body, symbolize_names: true)
|
|
28
|
+
else
|
|
29
|
+
raise GoogleApis::Error.new(
|
|
30
|
+
code: response.response_code,
|
|
31
|
+
status: Rack::Utils::HTTP_STATUS_CODES[response.response_code],
|
|
32
|
+
message: "Unexpected HTTP response received (#{response.response_code}, #{@response.content_type})",
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if result[:error].present?
|
|
37
|
+
@error = GoogleApis::Error.new(**result[:error])
|
|
38
|
+
else
|
|
39
|
+
@content_text = result.dig(:candidates, 0, :content, :parts, 0, :text)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
self
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
@error = e
|
|
45
|
+
raise e
|
|
46
|
+
ensure
|
|
47
|
+
report_error
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def success?
|
|
51
|
+
result.present? && content_text.present?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def content_json
|
|
55
|
+
return @content_json if instance_variable_defined?(:@content_json)
|
|
56
|
+
|
|
57
|
+
@content_json = JSON.parse(content_text, symbolize_names: true)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def inspect
|
|
61
|
+
"#<#{self.class.name} result: #{@result.inspect} error: #{@error.inspect}>"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def url
|
|
67
|
+
"https://aiplatform.googleapis.com/v1#{@parent}#{@model}:generateContent"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def report_error
|
|
71
|
+
return if error.nil?
|
|
72
|
+
|
|
73
|
+
if defined?(Sentry)
|
|
74
|
+
Sentry.add_breadcrumb(sentry_breadcrumb(error))
|
|
75
|
+
else
|
|
76
|
+
Rails.logger.error(error)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def sentry_breadcrumb(error)
|
|
81
|
+
Sentry::Breadcrumb.new(
|
|
82
|
+
type: "http",
|
|
83
|
+
category: "gemini",
|
|
84
|
+
data: {
|
|
85
|
+
url:,
|
|
86
|
+
method: "POST",
|
|
87
|
+
status_code: error.code,
|
|
88
|
+
reason: error.message,
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -8,8 +8,8 @@ module Katalyst
|
|
|
8
8
|
class AssessmentService
|
|
9
9
|
attr_accessor :response, :result, :error
|
|
10
10
|
|
|
11
|
-
def self.call(parent:, credentials: GoogleApis.credentials
|
|
12
|
-
new(credentials:, parent:).call(
|
|
11
|
+
def self.call(assessment:, parent:, credentials: GoogleApis.credentials)
|
|
12
|
+
new(credentials:, parent:).call(assessment:)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def initialize(credentials:, parent:)
|
|
@@ -23,18 +23,22 @@ module Katalyst
|
|
|
23
23
|
@credentials.apply!(http.headers)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
self
|
|
29
|
-
rescue Curl::Easy::Error => e
|
|
30
|
-
if defined?(Sentry)
|
|
31
|
-
Sentry.add_breadcrumb(sentry_breadcrumb(e))
|
|
26
|
+
if @response.content_type == "application/json"
|
|
27
|
+
@result = JSON.parse(response.body, symbolize_names: true)
|
|
32
28
|
else
|
|
33
|
-
|
|
29
|
+
raise GoogleApis::Error.new(
|
|
30
|
+
code: response.response_code,
|
|
31
|
+
status: Rack::Utils::HTTP_STATUS_CODES[response.response_code],
|
|
32
|
+
message: "Unexpected HTTP response received (#{response.response_code}, #{@response.content_type})",
|
|
33
|
+
)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
@error = e
|
|
37
36
|
self
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
@error = e
|
|
39
|
+
raise e
|
|
40
|
+
ensure
|
|
41
|
+
report_error
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def valid?
|
|
@@ -63,14 +67,26 @@ module Katalyst
|
|
|
63
67
|
"https://recaptchaenterprise.googleapis.com/v1/#{@parent}/assessments"
|
|
64
68
|
end
|
|
65
69
|
|
|
70
|
+
def report_error
|
|
71
|
+
return if error.nil?
|
|
72
|
+
|
|
73
|
+
if defined?(Sentry)
|
|
74
|
+
Sentry.add_breadcrumb(sentry_breadcrumb(error))
|
|
75
|
+
else
|
|
76
|
+
Rails.logger.error(error)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
66
80
|
def sentry_breadcrumb(error)
|
|
67
81
|
Sentry::Breadcrumb.new(
|
|
68
|
-
type:
|
|
69
|
-
category:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
type: "http",
|
|
83
|
+
category: "recaptcha",
|
|
84
|
+
data: {
|
|
85
|
+
url:,
|
|
86
|
+
method: "POST",
|
|
87
|
+
status_code: error.code,
|
|
88
|
+
reason: error.message,
|
|
89
|
+
},
|
|
74
90
|
)
|
|
75
91
|
end
|
|
76
92
|
end
|
data/config/ci.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Run using bin/ci
|
|
4
|
+
|
|
5
|
+
CI.run do
|
|
6
|
+
step "Setup", "bin/setup"
|
|
7
|
+
|
|
8
|
+
step "Style: Ruby", "bundle exec rubocop --no-server"
|
|
9
|
+
|
|
10
|
+
step "Style: JS/CSS", "bundle exec rake prettier:lint"
|
|
11
|
+
|
|
12
|
+
step "Security: Brakeman vulnerability audit", "bundle exec brakeman -q -w2"
|
|
13
|
+
|
|
14
|
+
step "Tests: rspec", "bundle exec rspec"
|
|
15
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: katalyst-google-apis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Katalyst Interactive
|
|
@@ -77,8 +77,11 @@ files:
|
|
|
77
77
|
- app/helpers/katalyst/google_apis/form_builder.rb
|
|
78
78
|
- app/helpers/katalyst/google_apis/govuk_form_builder.rb
|
|
79
79
|
- app/services/katalyst/google_apis/credentials.rb
|
|
80
|
+
- app/services/katalyst/google_apis/error.rb
|
|
81
|
+
- app/services/katalyst/google_apis/gemini/generate_content_service.rb
|
|
80
82
|
- app/services/katalyst/google_apis/recaptcha/assessment_service.rb
|
|
81
83
|
- app/validators/recaptcha_validator.rb
|
|
84
|
+
- config/ci.rb
|
|
82
85
|
- config/importmap.rb
|
|
83
86
|
- config/locales/en.yml
|
|
84
87
|
- lib/katalyst-google-apis.rb
|
|
@@ -106,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
106
109
|
- !ruby/object:Gem::Version
|
|
107
110
|
version: '0'
|
|
108
111
|
requirements: []
|
|
109
|
-
rubygems_version:
|
|
112
|
+
rubygems_version: 4.0.3
|
|
110
113
|
specification_version: 4
|
|
111
114
|
summary: Google REST APIs for use in Rails projects
|
|
112
115
|
test_files: []
|