pg_rails 7.0.8.pre.alpha.83 → 7.0.8.pre.alpha.84
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/pg_engine/app/controllers/pg_engine/base_controller.rb +3 -0
- data/pg_engine/app/controllers/public/webhooks_controller.rb +70 -3
- data/pg_engine/lib/pg_engine/mailgun/log_sync.rb +0 -2
- data/pg_engine/lib/pg_engine/utils/check_invalid_records.rb +39 -0
- data/pg_engine/lib/pg_engine/utils/pg_logger.rb +12 -1
- data/pg_engine/spec/controllers/pg_engine/base_controller_spec.rb +11 -0
- data/pg_engine/spec/controllers/public/webhooks_controller_spec.rb +122 -22
- data/pg_engine/spec/lib/pg_engine/mailgun/log_sync_spec.rb +1 -1
- data/pg_layout/app/views/layouts/pg_layout/base.html.slim +11 -0
- data/pg_rails/lib/pg_rails/rspec_logger_matchers.rb +62 -0
- data/pg_rails/lib/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c61900d2d0b3a827718b84a3cab67f4851b2eb3087e7fbc1d313f0acf163bf3
|
|
4
|
+
data.tar.gz: bfca92cbb58ba292877ccb61b15b08049a0d800fdc13408f82647e9ea7c66ee9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2282deaa98881e20bd0b754c6496fc9da679214760ea45fd5f8c2f0b64a0903e1557cffb2f8d4c13b156d46e62b237113bf8f24bdba676436a404eb1e57e37c5
|
|
7
|
+
data.tar.gz: d9ece2e6b39c53169548c253afdb18fb9707d76bc762b0b86b7cc2656e563a4efea6d303f51814aac22926effd37ef1ec1d6c6a6d00bded153476864586f6b14
|
|
@@ -1,10 +1,77 @@
|
|
|
1
1
|
module Public
|
|
2
2
|
class WebhooksController < PublicController
|
|
3
|
+
skip_before_action :verify_authenticity_token
|
|
4
|
+
|
|
5
|
+
before_action :verify_signature, only: :mailgun
|
|
6
|
+
|
|
7
|
+
rescue_from StandardError do
|
|
8
|
+
pg_err 'webhook internal server error', request.body.read
|
|
9
|
+
head :internal_server_error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rescue_from ActionDispatch::Http::Parameters::ParseError do
|
|
13
|
+
pg_warn 'webhook parser error', request.body.read
|
|
14
|
+
head :bad_request
|
|
15
|
+
end
|
|
16
|
+
|
|
3
17
|
def mailgun
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
if PgEngine::Mailgun::LogSync.digest(params['event-data'])
|
|
19
|
+
head :ok
|
|
20
|
+
else
|
|
21
|
+
# Si no se guardó el log es porque ya existía un log con ese ID
|
|
22
|
+
# Mando :not_acceptable (406) para que Mailgun no vuelva a intentar
|
|
23
|
+
# https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages
|
|
24
|
+
pg_warn 'ya existía un log con ese id, raaaaro', params
|
|
25
|
+
head :not_acceptable
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def used_tokens
|
|
32
|
+
Kredis.unique_list 'mailgun_webhook_used_tokens'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def not_used_token(token)
|
|
36
|
+
if used_tokens.elements.include?(token)
|
|
37
|
+
pg_warn 'Mailgun Webhook: refusing used token'
|
|
38
|
+
head :ok
|
|
39
|
+
|
|
40
|
+
false
|
|
41
|
+
else
|
|
42
|
+
used_tokens << token
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def timestamp_not_too_far(timestamp)
|
|
48
|
+
if (Time.zone.at(timestamp.to_i) - Time.zone.now).abs > 1.hour
|
|
49
|
+
pg_warn 'Mailgun Webhook: refusing due to timestamp too far'
|
|
50
|
+
head :ok
|
|
51
|
+
|
|
52
|
+
false
|
|
53
|
+
else
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def verify_signature
|
|
59
|
+
timestamp = params['signature']['timestamp']
|
|
60
|
+
token = params['signature']['token']
|
|
61
|
+
signature = params['signature']['signature']
|
|
62
|
+
hexdigest = encode(timestamp + token)
|
|
63
|
+
|
|
64
|
+
return unless not_used_token(token)
|
|
65
|
+
return unless timestamp_not_too_far(timestamp)
|
|
66
|
+
return unless hexdigest != signature
|
|
67
|
+
|
|
68
|
+
pg_warn 'Mailgun Webhook: refusing invalid signature'
|
|
7
69
|
head :ok
|
|
8
70
|
end
|
|
71
|
+
|
|
72
|
+
def encode(data)
|
|
73
|
+
webhook_secret_key = Rails.application.credentials.dig(:mailgun, :webhook_secret_key)
|
|
74
|
+
OpenSSL::HMAC.hexdigest('sha256', webhook_secret_key, data)
|
|
75
|
+
end
|
|
9
76
|
end
|
|
10
77
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# :nocov:
|
|
2
|
+
module PgEngine
|
|
3
|
+
module Utils
|
|
4
|
+
class CheckInvalidRecords
|
|
5
|
+
def run
|
|
6
|
+
invalids = []
|
|
7
|
+
classes.each do |klass|
|
|
8
|
+
klass.find_each do |record|
|
|
9
|
+
invalids << record unless record.valid?
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
invalids.map do |r|
|
|
13
|
+
[
|
|
14
|
+
(r.account.to_s if r.respond_to?(:account)),
|
|
15
|
+
r.class.to_s,
|
|
16
|
+
r.id,
|
|
17
|
+
r.errors.full_messages
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def classes
|
|
23
|
+
ActiveRecord::Base.descendants - ignored_classes
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ignored_classes
|
|
27
|
+
[
|
|
28
|
+
ActiveStorage::Record,
|
|
29
|
+
PgEngine::BaseRecord,
|
|
30
|
+
ActiveAdmin::Comment,
|
|
31
|
+
Audited::Audit,
|
|
32
|
+
ActiveStorage::Blob,
|
|
33
|
+
ApplicationRecord
|
|
34
|
+
]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
# :nocov:
|
|
@@ -37,6 +37,10 @@ end
|
|
|
37
37
|
|
|
38
38
|
module PgEngine
|
|
39
39
|
class PgLogger
|
|
40
|
+
def self.test_logged_messages
|
|
41
|
+
@test_logged_messages ||= []
|
|
42
|
+
end
|
|
43
|
+
|
|
40
44
|
class << self
|
|
41
45
|
def log(type, *args)
|
|
42
46
|
notify_all(build_msg(*args), type)
|
|
@@ -48,6 +52,7 @@ module PgEngine
|
|
|
48
52
|
send_to_logger(mensaje, type)
|
|
49
53
|
send_to_rollbar(mensaje, type)
|
|
50
54
|
send_to_stdout(mensaje, type) if ENV.fetch('LOG_TO_STDOUT', nil)
|
|
55
|
+
save_internal(mensaje, type) if Rails.env.test?
|
|
51
56
|
nil
|
|
52
57
|
end
|
|
53
58
|
|
|
@@ -68,6 +73,10 @@ module PgEngine
|
|
|
68
73
|
Rails.logger.send(type, rainbow_wrap(mensaje, type))
|
|
69
74
|
end
|
|
70
75
|
|
|
76
|
+
def save_internal(mensaje, type)
|
|
77
|
+
PgEngine::PgLogger.test_logged_messages << [type, mensaje]
|
|
78
|
+
end
|
|
79
|
+
|
|
71
80
|
# Format
|
|
72
81
|
|
|
73
82
|
# TODO: loguear time
|
|
@@ -117,7 +126,9 @@ module PgEngine
|
|
|
117
126
|
|
|
118
127
|
def cleaner
|
|
119
128
|
bc = ActiveSupport::BacktraceCleaner.new
|
|
120
|
-
bc.
|
|
129
|
+
bc.remove_silencers!
|
|
130
|
+
pattern = %r{\A[^/]+ \([\w.]+\) }
|
|
131
|
+
bc.add_silencer { |line| pattern.match?(line) && !line.match?(/pg_contable|pg_rails/) }
|
|
121
132
|
bc.add_silencer { |line| /pg_logger/.match?(line) }
|
|
122
133
|
bc
|
|
123
134
|
end
|
|
@@ -60,6 +60,17 @@ describe DummyBaseController do
|
|
|
60
60
|
expect(response.body).to include '<turbo-stream action="remove" targets=".modal">'
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
|
+
|
|
64
|
+
context 'cuando acepta json' do
|
|
65
|
+
before do
|
|
66
|
+
request.headers['Accept'] = 'application/json'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
fit do
|
|
70
|
+
subject
|
|
71
|
+
expect(response).to have_http_status(:internal_server_error)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
63
74
|
end
|
|
64
75
|
|
|
65
76
|
describe 'not_authorized' do
|
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
require 'rails_helper'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
RSpec::Matchers.define_negated_matcher :not_change, :change
|
|
4
|
+
|
|
5
|
+
def build_body(log_id, signature, timestamp)
|
|
6
|
+
<<~JSON
|
|
7
|
+
{
|
|
8
|
+
"signature": {
|
|
9
|
+
"timestamp": "#{timestamp}",
|
|
10
|
+
"token": "6fcc81a280a2458de8c83bf22dde8c71485be1d51333d3427e",
|
|
11
|
+
"signature": "#{signature}"
|
|
12
|
+
},
|
|
13
|
+
"event-data": {
|
|
14
|
+
"timestamp": 1715014542.2477064,
|
|
15
|
+
"ip": "66.102.8.333",
|
|
16
|
+
"event": "delivered",
|
|
17
|
+
"id": "#{log_id}",
|
|
18
|
+
"severity":"temporary",
|
|
19
|
+
"log-level": "info",
|
|
20
|
+
"message": {
|
|
21
|
+
"headers": { "message-id": "msgid@fakeapp2024.mail" }
|
|
22
|
+
},
|
|
23
|
+
"recipient": "natprobel@fakemail.com"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
JSON
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
fdescribe Public::WebhooksController do
|
|
30
|
+
include ActiveSupport::Testing::TimeHelpers
|
|
31
|
+
|
|
32
|
+
before { travel_to Time.zone.at(1_716_564_587) }
|
|
33
|
+
|
|
4
34
|
describe '#mailgun' do
|
|
5
35
|
subject do
|
|
6
36
|
post :mailgun, body:, as: :json
|
|
7
37
|
end
|
|
8
38
|
|
|
39
|
+
let(:signature) { 'c524037907046276117758afae8a340e77a43a6e48eb35a9521426e7a3ff675b' }
|
|
40
|
+
let(:log_id) { 'log_2' }
|
|
41
|
+
let(:timestamp) { '1716564587' }
|
|
42
|
+
|
|
9
43
|
let(:body) do
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
"signature": {
|
|
13
|
-
"timestamp": "1529006854",
|
|
14
|
-
"token": "a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0",
|
|
15
|
-
"signature": "d2271d12299f6592d9d44cd9d250f0704e4674c30d79d07c47a66f95ce71cf55"
|
|
16
|
-
},
|
|
17
|
-
"event-data": {
|
|
18
|
-
"timestamp": 1715014542.2477064,
|
|
19
|
-
"ip": "66.102.8.333",
|
|
20
|
-
"event": "delivered",
|
|
21
|
-
"id": "log_2",
|
|
22
|
-
"severity":"temporary",
|
|
23
|
-
"log-level": "info",
|
|
24
|
-
"message": {
|
|
25
|
-
"headers": { "message-id": "msgid@fakeapp2024.mail" }
|
|
26
|
-
},
|
|
27
|
-
"recipient": "natprobel@fakemail.com"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
JSON
|
|
44
|
+
build_body(log_id, signature, timestamp)
|
|
31
45
|
end
|
|
32
46
|
|
|
33
47
|
it do
|
|
@@ -38,5 +52,91 @@ describe Public::WebhooksController do
|
|
|
38
52
|
it do
|
|
39
53
|
expect { subject }.to change(EmailLog, :count).by(1)
|
|
40
54
|
end
|
|
55
|
+
|
|
56
|
+
context 'cuando tira internal server error' do
|
|
57
|
+
let(:body) { '{ "este json": "no me sirve" }' }
|
|
58
|
+
|
|
59
|
+
it do
|
|
60
|
+
subject
|
|
61
|
+
expect(response).to have_http_status(:internal_server_error)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
fit do
|
|
65
|
+
expect { subject }.to have_errored('internal server error')
|
|
66
|
+
.and(have_errored('no me sirve'))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context 'cuando no es un json' do
|
|
71
|
+
let(:body) { 'mal json' }
|
|
72
|
+
|
|
73
|
+
it do
|
|
74
|
+
subject
|
|
75
|
+
expect(response).to have_http_status(:bad_request)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
fit do
|
|
79
|
+
expect { subject }.to have_warned('parser error').and(have_warned('mal json'))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'cuando ya existe un log con ese ID' do
|
|
84
|
+
before do
|
|
85
|
+
create(:email_log, log_id:)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it do
|
|
89
|
+
expect { subject }.to have_warned('ya existía un log con ese id')
|
|
90
|
+
.and(not_change(EmailLog, :count))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
shared_context 'todo bien pero no guarda el log' do
|
|
95
|
+
it 'responds ok' do
|
|
96
|
+
subject
|
|
97
|
+
expect(response).to have_http_status(:ok)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'no guarda el log ;)' do
|
|
101
|
+
expect { subject }.not_to change(EmailLog, :count)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context 'si la signature no es válida' do
|
|
106
|
+
let(:signature) { 'no válida' }
|
|
107
|
+
|
|
108
|
+
it_behaves_like 'todo bien pero no guarda el log'
|
|
109
|
+
|
|
110
|
+
fit do
|
|
111
|
+
expect { subject }.to have_warned('refusing invalid signature')
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context 'cuando ya se usó el token' do
|
|
116
|
+
subject do
|
|
117
|
+
post :mailgun, body: build_body('otro id', signature, timestamp), as: :json
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
before do
|
|
121
|
+
post :mailgun, body: build_body(log_id, signature, timestamp), as: :json
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it_behaves_like 'todo bien pero no guarda el log'
|
|
125
|
+
|
|
126
|
+
fit do
|
|
127
|
+
expect { subject }.to have_warned('refusing used token')
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context 'cuando la timestamp está muy lejos' do
|
|
132
|
+
let(:timestamp) { '1111111' }
|
|
133
|
+
let(:signature) { '869c06cea4be27321a6a7c6a8e3d0668bb9c2b5b3de5532f644dba181a157d9a' }
|
|
134
|
+
|
|
135
|
+
it_behaves_like 'todo bien pero no guarda el log'
|
|
136
|
+
|
|
137
|
+
fit do
|
|
138
|
+
expect { subject }.to have_warned('refusing due to timestamp')
|
|
139
|
+
end
|
|
140
|
+
end
|
|
41
141
|
end
|
|
42
142
|
end
|
|
@@ -33,6 +33,17 @@ html
|
|
|
33
33
|
- if @rollbar_token.present?
|
|
34
34
|
meta name="rollbar-token" content="#{@rollbar_token}"
|
|
35
35
|
meta name="rollbar-env" content="#{Rails.env}"
|
|
36
|
+
|
|
37
|
+
link rel="preconnect" href="https://fonts.googleapis.com"
|
|
38
|
+
link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"
|
|
39
|
+
link href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap" rel="stylesheet"
|
|
40
|
+
css:
|
|
41
|
+
body {
|
|
42
|
+
font-family: "Ubuntu", sans-serif;
|
|
43
|
+
font-weight: 400;
|
|
44
|
+
font-style: normal;
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
body
|
|
37
48
|
= render partial: 'pg_layout/sidebar_mobile'
|
|
38
49
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# :nocov:
|
|
2
|
+
module PgEngine
|
|
3
|
+
module Matchers
|
|
4
|
+
module PgLogger
|
|
5
|
+
class Base < RSpec::Rails::Matchers::BaseMatcher
|
|
6
|
+
def initialize(text, level)
|
|
7
|
+
super()
|
|
8
|
+
@text = text
|
|
9
|
+
@level = level
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class PgHaveLogged < Base
|
|
14
|
+
def matches?(proc)
|
|
15
|
+
msg = 'have_logged only support block expectations'
|
|
16
|
+
raise ArgumentError, msg unless proc.is_a?(Proc)
|
|
17
|
+
|
|
18
|
+
original_messages = Set.new(PgEngine::PgLogger.test_logged_messages)
|
|
19
|
+
proc.call
|
|
20
|
+
logged_messages = Set.new(PgEngine::PgLogger.test_logged_messages)
|
|
21
|
+
|
|
22
|
+
@new_messages = logged_messages - original_messages
|
|
23
|
+
@new_messages.any? do |level, message|
|
|
24
|
+
if @text.present? && @level.present?
|
|
25
|
+
level == @level && message.include?(@text)
|
|
26
|
+
elsif @text.present?
|
|
27
|
+
message.include? @text
|
|
28
|
+
elsif @level.present?
|
|
29
|
+
level == @level
|
|
30
|
+
else
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failure_message
|
|
37
|
+
msg = "expected to #{@level || log}"
|
|
38
|
+
msg << "with text: #{@text}" if @text.present?
|
|
39
|
+
return unless @new_messages.any?
|
|
40
|
+
|
|
41
|
+
msg << "\nLogged messages:"
|
|
42
|
+
@new_messages.each do |level, message|
|
|
43
|
+
msg << "\n #{level}: #{message[0..200]}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def supports_block_expectations?
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def have_errored(text = nil) # rubocop:disable Naming/PredicateName
|
|
54
|
+
PgLogger::PgHaveLogged.new(text, :error)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def have_warned(text = nil) # rubocop:disable Naming/PredicateName
|
|
58
|
+
PgLogger::PgHaveLogged.new(text, :warn)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
# :nocov:
|
data/pg_rails/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.0.8.pre.alpha.
|
|
4
|
+
version: 7.0.8.pre.alpha.84
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Martín Rosso
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-05-
|
|
11
|
+
date: 2024-05-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -1066,6 +1066,7 @@ files:
|
|
|
1066
1066
|
- pg_engine/lib/pg_engine/error.rb
|
|
1067
1067
|
- pg_engine/lib/pg_engine/mailgun/log_sync.rb
|
|
1068
1068
|
- pg_engine/lib/pg_engine/route_helpers.rb
|
|
1069
|
+
- pg_engine/lib/pg_engine/utils/check_invalid_records.rb
|
|
1069
1070
|
- pg_engine/lib/pg_engine/utils/pdf_preview_generator.rb
|
|
1070
1071
|
- pg_engine/lib/pg_engine/utils/pg_logger.rb
|
|
1071
1072
|
- pg_engine/lib/tasks/auto_anotar_modelos.rake
|
|
@@ -1172,6 +1173,7 @@ files:
|
|
|
1172
1173
|
- pg_rails/lib/pg_rails/current_attributes_support.rb
|
|
1173
1174
|
- pg_rails/lib/pg_rails/dotenv_support.rb
|
|
1174
1175
|
- pg_rails/lib/pg_rails/redis_support.rb
|
|
1176
|
+
- pg_rails/lib/pg_rails/rspec_logger_matchers.rb
|
|
1175
1177
|
- pg_rails/lib/pg_rails/vcr_support.rb
|
|
1176
1178
|
- pg_rails/lib/version.rb
|
|
1177
1179
|
- pg_rails/scss/bootstrap_overrides.scss
|