pg_rails 7.0.8.pre.alpha.56 → 7.0.8.pre.alpha.57
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/admin/audits.rb +2 -0
- data/pg_engine/app/admin/emails.rb +22 -0
- data/pg_engine/app/controllers/admin/emails_controller.rb +41 -0
- data/pg_engine/app/decorators/email_decorator.rb +20 -0
- data/pg_engine/app/mailers/pg_engine/base_mailer.rb +34 -0
- data/pg_engine/app/models/email.rb +81 -0
- data/pg_engine/app/policies/email_policy.rb +35 -0
- data/pg_engine/app/views/admin/emails/_form.html.slim +16 -0
- data/pg_engine/app/views/admin/emails/show.html.slim +29 -0
- data/pg_engine/config/initializers/action_mailer.rb +2 -0
- data/pg_engine/config/routes.rb +214 -0
- data/pg_engine/db/migrate/20240506194106_create_emails.rb +35 -0
- data/pg_engine/lib/pg_engine/email_observer.rb +66 -0
- data/pg_engine/lib/pg_engine/mailgun/log_sync.rb +71 -0
- data/pg_engine/lib/pg_engine.rb +4 -0
- data/pg_engine/spec/controllers/admin/emails_controller_spec.rb +216 -0
- data/pg_engine/spec/controllers/admin/users_controller_spec.rb +3 -3
- data/pg_engine/spec/factories/emails.rb +29 -0
- data/pg_engine/spec/lib/pg_engine/mailgun/log_sync_spec.rb +25 -0
- data/pg_engine/spec/models/email_spec.rb +13 -0
- data/pg_layout/app/views/layouts/pg_layout/mailer.html.slim +11 -0
- data/pg_layout/app/views/layouts/pg_layout/mailer.text.slim +4 -0
- data/pg_rails/lib/pg_rails/redis_support.rb +6 -0
- data/pg_rails/lib/version.rb +1 -1
- data/pg_scaffold/lib/generators/pg_rspec/scaffold/templates/controller_spec.rb +2 -2
- metadata +76 -2
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEngine
|
4
|
+
class EmailObserver
|
5
|
+
def self.delivered_email(message)
|
6
|
+
# content = get_content(message)
|
7
|
+
# subject = message.subject
|
8
|
+
|
9
|
+
# message.to puede ser un string o un array de strings
|
10
|
+
# to = [message.to].flatten.join(', ')
|
11
|
+
|
12
|
+
message_id = message.message_id
|
13
|
+
mailer = message.delivery_handler.to_s
|
14
|
+
status = get_status(message)
|
15
|
+
content_eml = message.encoded
|
16
|
+
return if message['email'].blank?
|
17
|
+
|
18
|
+
# El objeto Email ya estaba creado. Agarro ese objeto y le actualizo las cosas
|
19
|
+
email = message['email'].unparsed_value
|
20
|
+
|
21
|
+
email.update_columns(message_id:, mailer:, status:, content_eml:) # rubocop:disable Rails/SkipsModelValidations
|
22
|
+
# else
|
23
|
+
# TODO: crear email
|
24
|
+
# pg_warn 'El mail no tenía objeto Email asociado, se creó uno on the fly.', :warn
|
25
|
+
# # No hay objeto Email, entonces le creo uno para que quede logueado.
|
26
|
+
# # TODO: el content puede ser TXT
|
27
|
+
# Email.create!(message_id: message.message_id, body_html: message.encoded,
|
28
|
+
# subject: subject, recipient: recipient,
|
29
|
+
# date: DateTime.now, mailer: message.delivery_handler.to_s, associated: associated(message),
|
30
|
+
# status: get_status(message), observations: get_observations(message))
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.get_status(message)
|
34
|
+
message.perform_deliveries ? :sent : :blocked
|
35
|
+
end
|
36
|
+
|
37
|
+
# def self.get_html_or_text(message)
|
38
|
+
# message.body.parts.find { |p| p.content_type.match(/multipart/).present? }
|
39
|
+
# part = message.body.parts.find { |p| p.content_type.match(/html/).present? }
|
40
|
+
# part = message.body.parts.find { |p| p.content_type.match(/text/).present? } if part.nil?
|
41
|
+
# part = message if part.nil?
|
42
|
+
# part.body.raw_source
|
43
|
+
# end
|
44
|
+
|
45
|
+
# def self.associated(message)
|
46
|
+
# return if message['associated'].blank?
|
47
|
+
|
48
|
+
# message['associated'].unparsed_value
|
49
|
+
# end
|
50
|
+
|
51
|
+
# def self.get_observations(message)
|
52
|
+
# return if message['observations'].blank?
|
53
|
+
|
54
|
+
# message['observations'].unparsed_value
|
55
|
+
# end
|
56
|
+
|
57
|
+
# def self.get_content(message)
|
58
|
+
# multipart = message.body.parts.find { |p| p.content_type.match(/multipart/).present? }
|
59
|
+
# part = multipart.presence || message
|
60
|
+
# get_html_or_text(part)
|
61
|
+
# rescue StandardError => e
|
62
|
+
# pg_err e
|
63
|
+
# ''
|
64
|
+
# end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'mailgun'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module PgEngine
|
8
|
+
module Mailgun
|
9
|
+
class LogSync
|
10
|
+
def self.download # rubocop:disable Metrics/AbcSize
|
11
|
+
domain = ENV.fetch('MAILGUN_DOMAIN')
|
12
|
+
|
13
|
+
key = Rails.application.credentials.dig(:mailgun, :api_key)
|
14
|
+
mg_client = ::Mailgun::Client.new(key)
|
15
|
+
items = []
|
16
|
+
end_time = DateTime.now
|
17
|
+
start_time = DateTime.now - 3.days
|
18
|
+
range = 1.day
|
19
|
+
current = end_time - range
|
20
|
+
loop do
|
21
|
+
get = "#{domain}/events?begin=#{current.to_i}&end=#{(current + range).to_i}&limit=300"
|
22
|
+
result = mg_client.get(get)
|
23
|
+
result.to_h!
|
24
|
+
items.push(result.body['items'])
|
25
|
+
# puts "Current: #{current}. Items count: #{result.body['items'].length}"
|
26
|
+
current -= range
|
27
|
+
|
28
|
+
break if current < start_time
|
29
|
+
end
|
30
|
+
|
31
|
+
FileUtils.mkdir_p(dir)
|
32
|
+
|
33
|
+
File.write("#{dir}/#{domain}-#{Time.zone.now.strftime('%Y-%m-%d_%H.%M.%S')}.json", items.flatten.to_json)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.dir
|
37
|
+
@dir ||= if Rails.env.test?
|
38
|
+
File.expand_path 'tmp/mailgun_logs', Rails.root
|
39
|
+
else
|
40
|
+
File.expand_path 'log/mailgun_logs', Rails.root
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.sync_redis
|
45
|
+
json = []
|
46
|
+
Dir["#{dir}/*.json"].each do |file|
|
47
|
+
json.push(*JSON.parse(File.read(file)))
|
48
|
+
end
|
49
|
+
|
50
|
+
json.each do |i|
|
51
|
+
# [
|
52
|
+
# Time.at(i["timestamp"]).strftime('%y-%m-%d %H:%M %z'),
|
53
|
+
# i["message"]["headers"]["message-id"],
|
54
|
+
# # i["message"]["headers"]["message-id"][3..15],
|
55
|
+
# i["event"],
|
56
|
+
# i["recipient"],
|
57
|
+
# i["message"]["headers"]["from"],
|
58
|
+
# i["message"]["headers"]["subject"],
|
59
|
+
# ]
|
60
|
+
message_id = i['message']['headers']['message-id']
|
61
|
+
email = Email.where(message_id:).first
|
62
|
+
if email
|
63
|
+
email.logs << i.to_json
|
64
|
+
else
|
65
|
+
pg_warn "No existe el mail con message_id = #{message_id}", :warn
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/pg_engine/lib/pg_engine.rb
CHANGED
@@ -4,6 +4,8 @@ require_relative 'pg_engine/engine'
|
|
4
4
|
require_relative 'pg_engine/core_ext'
|
5
5
|
require_relative 'pg_engine/error'
|
6
6
|
require_relative 'pg_engine/configuracion'
|
7
|
+
require_relative 'pg_engine/email_observer'
|
8
|
+
require_relative 'pg_engine/mailgun/log_sync'
|
7
9
|
require_relative 'pg_engine/route_helpers'
|
8
10
|
require_relative 'pg_engine/utils/pg_logger'
|
9
11
|
require_relative 'pg_engine/utils/pdf_preview_generator'
|
@@ -45,6 +47,8 @@ require 'sassc'
|
|
45
47
|
require 'image_processing'
|
46
48
|
require 'hashid/rails'
|
47
49
|
require 'redis'
|
50
|
+
require 'kredis'
|
51
|
+
require 'mailgun-ruby'
|
48
52
|
|
49
53
|
if Rails.env.local?
|
50
54
|
require 'letter_opener'
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# generado con pg_rails
|
4
|
+
|
5
|
+
require 'rails_helper'
|
6
|
+
|
7
|
+
# This spec was generated by rspec-rails when you ran the scaffold generator.
|
8
|
+
# It demonstrates how one might use RSpec to specify the controller code that
|
9
|
+
# was generated by Rails when you ran the scaffold generator.
|
10
|
+
#
|
11
|
+
# It assumes that the implementation code is generated by the rails scaffold
|
12
|
+
# generator. If you are using any extension libraries to generate different
|
13
|
+
# controller code, this generated spec may or may not pass.
|
14
|
+
#
|
15
|
+
# It only uses APIs available in rails and/or rspec-rails. There are a number
|
16
|
+
# of tools you can use to make these specs even more expressive, but we're
|
17
|
+
# sticking to rails and rspec-rails APIs to keep things simple and stable.
|
18
|
+
#
|
19
|
+
# Compared to earlier versions of this generator, there is very limited use of
|
20
|
+
# stubs and message expectations in this spec. Stubs are only used when there
|
21
|
+
# is no simpler way to get a handle on the object needed for the example.
|
22
|
+
# Message expectations are only used when there is no simpler way to specify
|
23
|
+
# that an instance is receiving a specific message.
|
24
|
+
#
|
25
|
+
# Also compared to earlier versions of this generator, there are no longer any
|
26
|
+
# expectations of assigns and templates rendered. These features have been
|
27
|
+
# removed from Rails core in Rails 5, but can be added back in via the
|
28
|
+
# `rails-controller-testing` gem.
|
29
|
+
|
30
|
+
RSpec.describe Admin::EmailsController do
|
31
|
+
render_views
|
32
|
+
# This should return the minimal set of attributes required to create a valid
|
33
|
+
# Email. As you add validations to Email, be sure to
|
34
|
+
# adjust the attributes here as well.
|
35
|
+
let(:valid_attributes) do
|
36
|
+
attributes_for(:email)
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:invalid_attributes) do
|
40
|
+
{
|
41
|
+
from_address: nil
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:logged_user) { create :user, :developer }
|
46
|
+
|
47
|
+
before do
|
48
|
+
sign_in logged_user if logged_user.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'routing' do
|
52
|
+
it 'routes GET index correctly' do
|
53
|
+
route = { get: '/a/emails' }
|
54
|
+
expect(route).to route_to(controller: 'admin/emails', action: 'index')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'GET #content_eml' do
|
59
|
+
subject do
|
60
|
+
get :content_eml, params: { id: email.id }
|
61
|
+
end
|
62
|
+
|
63
|
+
let(:email) { create :email }
|
64
|
+
|
65
|
+
it do
|
66
|
+
subject
|
67
|
+
expect(response).to have_http_status(:ok)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'GET #index' do
|
72
|
+
subject do
|
73
|
+
get :index, params: {}
|
74
|
+
end
|
75
|
+
|
76
|
+
before { create :email }
|
77
|
+
|
78
|
+
it 'returns a success response' do
|
79
|
+
subject
|
80
|
+
expect(response).to be_successful
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when user is not logged in' do
|
84
|
+
let(:logged_user) { nil }
|
85
|
+
|
86
|
+
it 'redirects to login path' do
|
87
|
+
subject
|
88
|
+
expect(response).to redirect_to(new_user_session_path)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when se pide el excel' do
|
93
|
+
subject do
|
94
|
+
get :index, params: {}, format: 'xlsx'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns a success response' do
|
98
|
+
subject
|
99
|
+
expect(response).to be_successful
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'GET #show' do
|
105
|
+
it 'returns a success response' do
|
106
|
+
email = create(:email)
|
107
|
+
get :show, params: { id: email.to_param }
|
108
|
+
expect(response).to be_successful
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'GET #new' do
|
113
|
+
it 'returns a success response' do
|
114
|
+
get :new, params: {}
|
115
|
+
expect(response).to be_successful
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'GET #edit' do
|
120
|
+
it 'returns a success response' do
|
121
|
+
email = create(:email)
|
122
|
+
get :edit, params: { id: email.to_param }
|
123
|
+
expect(response).to be_successful
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe 'POST #create' do
|
128
|
+
context 'with valid params' do
|
129
|
+
it 'creates a new Email' do
|
130
|
+
expect do
|
131
|
+
post :create, params: { email: valid_attributes }
|
132
|
+
end.to change(Email, :count).by(1)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'redirects to the created email' do
|
136
|
+
post :create, params: { email: valid_attributes }
|
137
|
+
expect(response).to redirect_to([:admin, Email.last])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'with invalid params' do
|
142
|
+
it 'returns a unprocessable_entity response' do
|
143
|
+
post :create, params: { email: invalid_attributes }
|
144
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'renders the new template' do
|
148
|
+
post :create, params: { email: invalid_attributes }
|
149
|
+
expect(response).to render_template(:new)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'PUT #update' do
|
155
|
+
context 'with valid params' do
|
156
|
+
let(:new_attributes) do
|
157
|
+
attributes_for(:email)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'updates the requested email' do
|
161
|
+
email = create(:email)
|
162
|
+
put :update, params: { id: email.to_param, email: new_attributes }
|
163
|
+
email.reload
|
164
|
+
expect(email.from_address).to eq new_attributes[:from_address]
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'redirects to the email' do
|
168
|
+
email = create(:email)
|
169
|
+
put :update, params: { id: email.to_param, email: valid_attributes }
|
170
|
+
expect(response).to redirect_to([:admin, email])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'with invalid params' do
|
175
|
+
it 'returns a unprocessable_entity response' do
|
176
|
+
email = create(:email)
|
177
|
+
put :update, params: { id: email.to_param, email: invalid_attributes }
|
178
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'renders the edit template' do
|
182
|
+
email = create(:email)
|
183
|
+
put :update, params: { id: email.to_param, email: invalid_attributes }
|
184
|
+
expect(response).to render_template(:edit)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe 'DELETE #destroy' do
|
190
|
+
subject do
|
191
|
+
request.headers['Accept'] = 'text/vnd.turbo-stream.html,text/html'
|
192
|
+
delete :destroy, params: { id: email.to_param, redirect_to: redirect_url }
|
193
|
+
end
|
194
|
+
|
195
|
+
let!(:email) { create :email }
|
196
|
+
let(:redirect_url) { nil }
|
197
|
+
|
198
|
+
it 'destroys the requested email' do
|
199
|
+
expect { subject }.to change(Email, :count).by(-1)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'quita el elemento de la lista' do
|
203
|
+
subject
|
204
|
+
expect(response.body).to include('turbo-stream action="remove"')
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'si hay redirect_to' do
|
208
|
+
let(:redirect_url) { admin_emails_url }
|
209
|
+
|
210
|
+
it 'redirects to the emails list' do
|
211
|
+
subject
|
212
|
+
expect(response).to redirect_to(admin_emails_url)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -43,10 +43,10 @@ RSpec.describe Admin::UsersController do
|
|
43
43
|
}
|
44
44
|
end
|
45
45
|
|
46
|
-
let(:
|
46
|
+
let(:logged_user) { create :user, :admin }
|
47
47
|
|
48
48
|
before do
|
49
|
-
sign_in
|
49
|
+
sign_in logged_user
|
50
50
|
end
|
51
51
|
|
52
52
|
describe 'GET #index' do
|
@@ -66,7 +66,7 @@ RSpec.describe Admin::UsersController do
|
|
66
66
|
|
67
67
|
it do
|
68
68
|
subject
|
69
|
-
expect(assigns(:collection)).to eq [
|
69
|
+
expect(assigns(:collection)).to eq [logged_user]
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# generado con pg_rails
|
4
|
+
|
5
|
+
FactoryBot.define do
|
6
|
+
factory :email do
|
7
|
+
# accepted_at { "2024-05-06 16:41:06" }
|
8
|
+
# delivered_at { "2024-05-06 16:41:06" }
|
9
|
+
# opened_at { "2024-05-06 16:41:06" }
|
10
|
+
from_address { Faker::Internet.email }
|
11
|
+
from_name { Faker::Name.name }
|
12
|
+
reply_to { Faker::Internet.email }
|
13
|
+
to { Faker::Internet.email }
|
14
|
+
subject { Faker::Lorem.sentence }
|
15
|
+
body_input { Faker::Lorem.sentence }
|
16
|
+
# tags { Faker::Lorem.sentence }
|
17
|
+
# associated
|
18
|
+
# content_eml { Faker::Lorem.sentence }
|
19
|
+
# message_id { Faker::Lorem.sentence }
|
20
|
+
# mailer { Faker::Lorem.sentence }
|
21
|
+
# status_detail { Faker::Lorem.sentence }
|
22
|
+
# status { rand(1..9999) }
|
23
|
+
|
24
|
+
# trait :associated_existente do
|
25
|
+
# associated { nil }
|
26
|
+
# associated_id { Associated.pluck(:id).sample }
|
27
|
+
# end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe PgEngine::Mailgun::LogSync do
|
5
|
+
let(:instancia) { described_class }
|
6
|
+
|
7
|
+
describe '#download',
|
8
|
+
vcr: { cassette_name: 'mailgun/log_sync_download',
|
9
|
+
match_requests_on: %i[method host] } do
|
10
|
+
subject do
|
11
|
+
instancia.download
|
12
|
+
instancia.sync_redis
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
Dir["#{instancia.dir}/*.json"].each { |file| File.delete(file) }
|
17
|
+
end
|
18
|
+
|
19
|
+
let!(:email) { create :email, message_id: '66393f1bc7d4_47a5108ec1628f@notebook.mail' }
|
20
|
+
|
21
|
+
it do
|
22
|
+
expect { subject }.to change { email.logs.to_a.length }.from(0).to(3)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
html
|
2
|
+
body style="font-family: sans-serif"
|
3
|
+
= yield
|
4
|
+
|
5
|
+
footer style="margin-top: 2em;"
|
6
|
+
- if @footer_href.present?
|
7
|
+
= link_to @footer_href, rel: 'noreferrer', target: :_blank do
|
8
|
+
- if @footer_image_src.present?
|
9
|
+
= image_tag @footer_image_src, alt: @footer_image_alt
|
10
|
+
- else
|
11
|
+
= @footer_image_alt
|
data/pg_rails/lib/version.rb
CHANGED
@@ -170,7 +170,7 @@ RSpec.describe <%= controller_class_name %>Controller do
|
|
170
170
|
<% else -%>
|
171
171
|
post :create, params: { <%= nombre_tabla_completo_singular %>: valid_attributes }
|
172
172
|
<% end -%>
|
173
|
-
expect(response).to redirect_to(<%= class_name %>.last
|
173
|
+
expect(response).to redirect_to([:<%= ns_prefix.first %>, <%= class_name %>.last])
|
174
174
|
end
|
175
175
|
end
|
176
176
|
<% if attributes.any? { |at| at.required? } -%>
|
@@ -218,7 +218,7 @@ RSpec.describe <%= controller_class_name %>Controller do
|
|
218
218
|
<% else -%>
|
219
219
|
put :update, params: { id: <%= file_name %>.to_param, <%= nombre_tabla_completo_singular %>: valid_attributes }
|
220
220
|
<% end -%>
|
221
|
-
expect(response).to redirect_to(<%= file_name %>.decorate.target_object)
|
221
|
+
expect(response).to redirect_to([:<%= ns_prefix.first %>, <%= file_name %>.decorate.target_object])
|
222
222
|
end
|
223
223
|
end
|
224
224
|
<% if attributes.any? { |at| at.required? } -%>
|