katalyst-google-apis 1.1.1 → 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 +8 -5
- 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 +4 -1
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
|
|
@@ -111,20 +111,21 @@ function getMetaElement(name) {
|
|
|
111
111
|
function getCspNonce() {
|
|
112
112
|
const element = getMetaElement("csp-nonce");
|
|
113
113
|
if (element) {
|
|
114
|
-
const {nonce: nonce, content: content} = element;
|
|
114
|
+
const { nonce: nonce, content: content } = element;
|
|
115
115
|
return nonce === "" ? content : nonce;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function createRecaptchaScriptElement() {
|
|
120
120
|
const element = document.createElement("script");
|
|
121
|
-
element.src =
|
|
121
|
+
element.src =
|
|
122
|
+
"https://www.google.com/recaptcha/enterprise.js?render=explicit";
|
|
122
123
|
const cspNonce = getCspNonce();
|
|
123
124
|
if (cspNonce) {
|
|
124
125
|
element.setAttribute("nonce", cspNonce);
|
|
125
126
|
}
|
|
126
|
-
element.async = true
|
|
127
|
-
element.defer = true
|
|
127
|
+
element.async = true;
|
|
128
|
+
element.defer = true;
|
|
128
129
|
|
|
129
130
|
return element;
|
|
130
131
|
}
|
|
@@ -133,7 +134,9 @@ function createRecaptchaScriptElement() {
|
|
|
133
134
|
* Inject recaptcha enterprise script into the head, unless it is already there.
|
|
134
135
|
*/
|
|
135
136
|
function injectRecaptchaScripts() {
|
|
136
|
-
let element = document.head.querySelector(
|
|
137
|
+
let element = document.head.querySelector(
|
|
138
|
+
"script[src*='recaptcha/enterprise']",
|
|
139
|
+
);
|
|
137
140
|
if (!element) {
|
|
138
141
|
element = createRecaptchaScriptElement();
|
|
139
142
|
document.head.appendChild(element);
|
|
@@ -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
|