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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9802ce92e121059dcd7f6871d7959851db21471739bbd3489769731fbd84bf3c
4
- data.tar.gz: f759a549e162e91258481274c603f594e59553e6f9b5dc2186912e1adc8f1149
3
+ metadata.gz: 9811888c443080f2c6e2107db38c63738cfebe5bbcb912779792924401301db1
4
+ data.tar.gz: c247d6f9e6587a57d7375766e3db44f20c6278236016a44eab54a988f3449a23
5
5
  SHA512:
6
- metadata.gz: 23c8f9427fd91bfecd9b087a4251e5af53fd95eaedee09d46ff6c3ab3881d54f442e22b12e3e2565100050af61aea95152f1dd298fab3bdb2bb0ebd057ebc057
7
- data.tar.gz: c82a3865797c7c22f575847bccf42b53ac7b2edf9d85577328a7d4b5c23dceafa364a59186bb0fa6dcafd8051016ea854ebfb19734142b3e830682c293bed751
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
- @result = JSON.parse(@response.body, symbolize_names: true)
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
- Rails.logger.error(e)
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: "http",
69
- category: "recaptcha",
70
- url:,
71
- method: "POST",
72
- status_code: error.code,
73
- reason: error.message,
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.1.0
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: 3.6.7
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: []