Merge pull request #2797 from postalserver/rubocop

Code Quality Improvements
This commit is contained in:
Adam Cooke
2024-02-13 14:24:41 +00:00
committed by GitHub
221 changed files with 1026 additions and 386 deletions
+151 -12
View File
@@ -1,35 +1,174 @@
inherit_from: AllCops:
- https://dev.k.io/rubocop/rubocop.rails.yml TargetRubyVersion: 3.0
NewCops: enable
Exclude:
- "bin/*"
- "db/schema.rb"
# Fixes missing gem exception when running Rubocop on GitHub Actions.
- "vendor/bundle/**/*"
# Always use double quotes
Style/StringLiterals: Style/StringLiterals:
EnforcedStyle: double_quotes EnforcedStyle: double_quotes
Style/WordArray: # We prefer arrays of symbols to look like an array of symbols.
# For example: [:one, :two, :three] as opposed to %i[one two three]
Style/SymbolArray:
EnforcedStyle: brackets EnforcedStyle: brackets
Layout/LineLength: # There should always be empty lines inside a class. For example
#
# class MyExample
#
# def some_method
# end
#
# end
Layout/EmptyLinesAroundClassBody:
EnforcedStyle: empty_lines
# We want to keep attr_* definitions separated on their own lines, rather than
# all of them collapsed into a single attr_* call. The collapsed/grouped variant
# is harder to read, and harder to see what's been changed in diffs.
Style/AccessorGrouping:
Enabled: false Enabled: false
# Blocks are slightly different to classes, in these cases there should
# not be new lines around the contents of the block.
#
# proc do
# # Do something
# end
Layout/EmptyLinesAroundBlockBody:
EnforcedStyle: no_empty_lines
# Modules are the same as classes unless they're being used for namespacing
# purposes in which case there should not be new lines.
Layout/EmptyLinesAroundModuleBody:
EnforcedStyle: empty_lines_except_namespace
# Space is required following -> when writing a lambda:
#
# somethign = -> (var) { block }
Layout/SpaceInLambdaLiteral:
EnforcedStyle: require_space
Layout/FirstHashElementIndentation:
Enabled: false
# We don't mind setting assignments in conditions so this has been disabled to
# allow `if something = something_else` without worrying about brackets.
Lint/AssignmentInCondition:
Enabled: false
# Top level documentation is quite rare...
Style/Documentation:
Enabled: false
# We want to allow inner slashes in a regexp to be used when using /xxx/ form.
Style/RegexpLiteral:
AllowInnerSlashes: true
# Blocks of if statements are perfectly fine and usually more readable than
# putting everything onto a single line just because we can.
Style/IfUnlessModifier:
Enabled: false
# We prefer assignments to happen within the condition rather than setting a
# variable to the result of a condition.
Style/ConditionalAssignment:
EnforcedStyle: assign_inside_condition
IncludeTernaryExpressions: false
# Empty methods should not be compacted onto a single line
Style/EmptyMethod:
EnforcedStyle: expanded
# As above, just flag them.
Lint/UnusedBlockArgument:
AutoCorrect: false
# While we don't want to make heavy use of get_ or set_ methods we do often need
# to use these when we want to refer to actually getting or setting something
# (usually from another API).
Naming/AccessorMethodName:
Enabled: false
# If we want a boolean called :true, we should be allowed that. These are likely
# not mistakes.
Lint/BooleanSymbol:
Enabled: false
# Using block.map(&:upcase) is not always the neatest way to show something. For
# example if you have a block that just calls one thing, you don't want it
# collapsed.
#
# action do |user|
# user.delete
# end
#
# This should be action(&:delete) because it is not clear what is actually
# happening without the context of knowing what the inner variable should be
# called.
Style/SymbolProc:
Enabled: false
# Allow a maxmium of 5 arguments and don't include keyword arguments
Metrics/ParameterLists:
Max: 5
CountKeywordArgs: false
# This cop checks for chaining of a block after another block that spans multiple lines.
Style/MultilineBlockChain:
Exclude:
- "spec/**/*.rb"
Metrics/AbcSize: Metrics/AbcSize:
Enabled: false Enabled: false
Style/FrozenStringLiteralComment:
Enabled: true
SafeAutoCorrect: true
Naming/PredicateName:
Enabled: false
Layout/LineLength:
# We want to reduce this back down to 120 but there are a fair number of offences
# of this which need addressing individually and carefully.
Max: 200
Metrics/PerceivedComplexity:
# As above, we want to enable this again in the future, but for now we'll just
# disable it entirely.
Enabled: false
Metrics/CyclomaticComplexity:
# As above.
Enabled: false
Metrics/MethodLength:
# As above.
Enabled: false
Metrics/BlockNesting:
# As above.
Enabled: false
Style/StringConcatenation:
Enabled: false
Metrics/BlockLength: Metrics/BlockLength:
Enabled: false Enabled: false
Metrics/ClassLength: Metrics/ClassLength:
Enabled: false Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength: Metrics/ModuleLength:
Enabled: false Enabled: false
Metrics/PerceivedComplexity: Lint/UnusedMethodArgument:
Enabled: false Enabled: false
Lint/MissingSuper: Style/SpecialGlobalVars:
Enabled: false Enabled: false
+5 -3
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
source "https://rubygems.org" source "https://rubygems.org"
gem "authie" gem "authie"
gem "autoprefixer-rails" gem "autoprefixer-rails"
@@ -10,12 +12,12 @@ gem "clockwork"
gem "dotenv-rails" gem "dotenv-rails"
gem "dynamic_form" gem "dynamic_form"
gem "encrypto_signo" gem "encrypto_signo"
gem "execjs", "~> 2.7", "< 2.8"
gem "foreman" gem "foreman"
gem "gelf" gem "gelf"
gem "haml" gem "haml"
gem "hashie" gem "hashie"
gem "highline", require: false gem "highline", require: false
gem "execjs", '~> 2.7', "< 2.8"
gem "jwt" gem "jwt"
gem "kaminari" gem "kaminari"
gem "mail" gem "mail"
@@ -28,8 +30,8 @@ gem "puma"
gem "rails", "= 6.1.7.6" gem "rails", "= 6.1.7.6"
gem "resolv", "~> 0.2.1" gem "resolv", "~> 0.2.1"
gem "secure_headers" gem "secure_headers"
gem 'sentry-rails' gem "sentry-rails"
gem 'sentry-ruby' gem "sentry-ruby"
gem "turbolinks", "~> 5" gem "turbolinks", "~> 5"
group :development, :assets do group :development, :assets do
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
authenticator :server do authenticator :server do
friendly_name "Server Authenticator" friendly_name "Server Authenticator"
header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", example: "f29a45f0d4e1744ebaee" header "X-Server-API-Key", "The API token for a server that you wish to authenticate with.", example: "f29a45f0d4e1744ebaee"
+4 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
controller :messages do controller :messages do
friendly_name "Messages API" friendly_name "Messages API"
description "This API allows you to access message details" description "This API allows you to access message details"
@@ -12,7 +14,7 @@ controller :messages do
action do action do
begin begin
message = identity.server.message(params.id) message = identity.server.message(params.id)
rescue Postal::MessageDB::Message::NotFound => e rescue Postal::MessageDB::Message::NotFound
error "MessageNotFound", id: params.id error "MessageNotFound", id: params.id
end end
structure :message, message, return: true structure :message, message, return: true
@@ -28,7 +30,7 @@ controller :messages do
action do action do
begin begin
message = identity.server.message(params.id) message = identity.server.message(params.id)
rescue Postal::MessageDB::Message::NotFound => e rescue Postal::MessageDB::Message::NotFound
error "MessageNotFound", id: params.id error "MessageNotFound", id: params.id
end end
message.deliveries.map do |d| message.deliveries.map do |d|
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
controller :send do controller :send do
friendly_name "Send API" friendly_name "Send API"
description "This API allows you to send messages" description "This API allows you to send messages"
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
structure :delivery do structure :delivery do
basic :id basic :id
basic :status basic :status
+4 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
structure :message do structure :message do
basic :id basic :id
basic :token basic :token
@@ -5,9 +7,9 @@ structure :message do
expansion(:status) do expansion(:status) do
{ {
status: o.status, status: o.status,
last_delivery_attempt: o.last_delivery_attempt ? o.last_delivery_attempt.to_f : nil, last_delivery_attempt: o.last_delivery_attempt&.to_f,
held: o.held, held: o.held,
hold_expiry: o.hold_expiry ? o.hold_expiry.to_f : nil hold_expiry: o.hold_expiry&.to_f
} }
end end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class AddressEndpointsController < ApplicationController class AddressEndpointsController < ApplicationController
include WithinOrganization include WithinOrganization
+3 -5
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require "authie/session" require "authie/session"
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
@@ -46,11 +48,7 @@ class ApplicationController < ActionController::Base
helper_method :page_title helper_method :page_title
def redirect_to_with_return_to(url, *args) def redirect_to_with_return_to(url, *args)
if params[:return_to].blank? || !params[:return_to].starts_with?("/") redirect_to url_with_return_to(url), *args
redirect_to url_with_return_to(url), *args
else
redirect_to url_with_return_to(url), *args
end
end end
def set_timezone def set_timezone
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module WithinOrganization module WithinOrganization
extend ActiveSupport::Concern extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class CredentialsController < ApplicationController class CredentialsController < ApplicationController
include WithinOrganization include WithinOrganization
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class DomainsController < ApplicationController class DomainsController < ApplicationController
include WithinOrganization include WithinOrganization
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class HelpController < ApplicationController class HelpController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class HTTPEndpointsController < ApplicationController class HTTPEndpointsController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class IPAddressesController < ApplicationController class IPAddressesController < ApplicationController
before_action :admin_required before_action :admin_required
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class IPPoolRulesController < ApplicationController class IPPoolRulesController < ApplicationController
include WithinOrganization include WithinOrganization
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class IPPoolsController < ApplicationController class IPPoolsController < ApplicationController
before_action :admin_required before_action :admin_required
@@ -31,7 +33,7 @@ class IPPoolsController < ApplicationController
def destroy def destroy
@ip_pool.destroy @ip_pool.destroy
redirect_to_with_json :ip_pools, notice: "IP pool has been removed successfully." redirect_to_with_json :ip_pools, notice: "IP pool has been removed successfully."
rescue ActiveRecord::DeleteRestrictionError => e rescue ActiveRecord::DeleteRestrictionError
redirect_to_with_json [:edit, @ip_pool], alert: "IP pool cannot be removed because it still has associated addresses or servers." redirect_to_with_json [:edit, @ip_pool], alert: "IP pool cannot be removed because it still has associated addresses or servers."
end end
+5 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class MessagesController < ApplicationController class MessagesController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -186,7 +188,7 @@ class MessagesController < ApplicationController
if qs[:before] if qs[:before]
begin begin
options[:where][:timestamp][:less_than] = get_time_from_string(qs[:before]).to_f options[:where][:timestamp][:less_than] = get_time_from_string(qs[:before]).to_f
rescue TimeUndetermined => e rescue TimeUndetermined
flash.now[:alert] = "Couldn't determine time for before from '#{qs[:before]}'" flash.now[:alert] = "Couldn't determine time for before from '#{qs[:before]}'"
end end
end end
@@ -194,7 +196,7 @@ class MessagesController < ApplicationController
if qs[:after] if qs[:after]
begin begin
options[:where][:timestamp][:greater_than] = get_time_from_string(qs[:after]).to_f options[:where][:timestamp][:greater_than] = get_time_from_string(qs[:after]).to_f
rescue TimeUndetermined => e rescue TimeUndetermined
flash.now[:alert] = "Couldn't determine time for after from '#{qs[:after]}'" flash.now[:alert] = "Couldn't determine time for after from '#{qs[:after]}'"
end end
end end
@@ -220,6 +222,7 @@ class MessagesController < ApplicationController
time = Chronic.parse(string, context: :past) time = Chronic.parse(string, context: :past)
end end
rescue StandardError rescue StandardError
time = nil
end end
raise TimeUndetermined, "Couldn't determine a suitable time from '#{string}'" if time.nil? raise TimeUndetermined, "Couldn't determine a suitable time from '#{string}'" if time.nil?
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class OrganizationIPPoolsController < ApplicationController class OrganizationIPPoolsController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class OrganizationsController < ApplicationController class OrganizationsController < ApplicationController
before_action :admin_required, only: [:new, :create, :delete, :destroy] before_action :admin_required, only: [:new, :create, :delete, :destroy]
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class RoutesController < ApplicationController class RoutesController < ApplicationController
include WithinOrganization include WithinOrganization
+8 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ServersController < ApplicationController class ServersController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -41,7 +43,12 @@ class ServersController < ApplicationController
def update def update
extra_params = [:spam_threshold, :spam_failure_threshold, :postmaster_address] extra_params = [:spam_threshold, :spam_failure_threshold, :postmaster_address]
extra_params += [:send_limit, :allow_sender, :privacy_mode, :log_smtp_data, :outbound_spam_threshold, :message_retention_days, :raw_message_retention_days, :raw_message_retention_size] if current_user.admin?
if current_user.admin?
extra_params += [:send_limit, :allow_sender, :privacy_mode, :log_smtp_data, :outbound_spam_threshold,
:message_retention_days, :raw_message_retention_days, :raw_message_retention_size]
end
if @server.update(safe_params(*extra_params)) if @server.update(safe_params(*extra_params))
redirect_to_with_json organization_server_path(organization, @server), notice: "Server settings have been updated" redirect_to_with_json organization_server_path(organization, @server), notice: "Server settings have been updated"
else else
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class SessionsController < ApplicationController class SessionsController < ApplicationController
layout "sub" layout "sub"
@@ -8,7 +10,7 @@ class SessionsController < ApplicationController
login(User.authenticate(params[:email_address], params[:password])) login(User.authenticate(params[:email_address], params[:password]))
flash[:remember_login] = true flash[:remember_login] = true
redirect_to_with_return_to root_path redirect_to_with_return_to root_path
rescue Postal::Errors::AuthenticationError => e rescue Postal::Errors::AuthenticationError
flash.now[:alert] = "The credentials you've provided are incorrect. Please check and try again." flash.now[:alert] = "The credentials you've provided are incorrect. Please check and try again."
render "new" render "new"
end end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class SMTPEndpointsController < ApplicationController class SMTPEndpointsController < ApplicationController
include WithinOrganization include WithinOrganization
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class TrackDomainsController < ApplicationController class TrackDomainsController < ApplicationController
include WithinOrganization include WithinOrganization
+5 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class UserController < ApplicationController class UserController < ApplicationController
skip_before_action :login_required, only: [:new, :create, :join] skip_before_action :login_required, only: [:new, :create, :join]
@@ -69,7 +71,9 @@ class UserController < ApplicationController
if @user.save if @user.save
if email_changed if email_changed
redirect_to_with_json verify_path(return_to: settings_path), notice: "Your settings have been updated successfully. As you've changed, your e-mail address you'll need to verify it before you can continue." redirect_to_with_json verify_path(return_to: settings_path),
notice: "Your settings have been updated successfully. As you've changed, your e-mail " \
"address you'll need to verify it before you can continue."
else else
redirect_to_with_json settings_path, notice: "Your settings have been updated successfully." redirect_to_with_json settings_path, notice: "Your settings have been updated successfully."
end end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class UsersController < ApplicationController class UsersController < ApplicationController
before_action :admin_required before_action :admin_required
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class WebhooksController < ApplicationController class WebhooksController < ApplicationController
include WithinOrganization include WithinOrganization
+7 -5
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ApplicationHelper module ApplicationHelper
def format_delivery_details(server, text) def format_delivery_details(server, text)
@@ -28,7 +30,7 @@ module ApplicationHelper
server_domains = server.domains.verified.order(:name) server_domains = server.domains.verified.order(:name)
unless server_domains.empty? unless server_domains.empty?
s << "<optgroup label='Server Domains'>" s << "<optgroup label='Server Domains'>"
for domain in server_domains server_domains.each do |domain|
selected = domain == selected_domain ? "selected='selected'" : "" selected = domain == selected_domain ? "selected='selected'" : ""
s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>" s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>"
end end
@@ -38,7 +40,7 @@ module ApplicationHelper
organization_domains = server.organization.domains.verified.order(:name) organization_domains = server.organization.domains.verified.order(:name)
unless organization_domains.empty? unless organization_domains.empty?
s << "<optgroup label='Organization Domains'>" s << "<optgroup label='Organization Domains'>"
for domain in organization_domains organization_domains.each do |domain|
selected = domain == selected_domain ? "selected='selected'" : "" selected = domain == selected_domain ? "selected='selected'" : ""
s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>" s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>"
end end
@@ -54,7 +56,7 @@ module ApplicationHelper
http_endpoints = server.http_endpoints.order(:name).to_a http_endpoints = server.http_endpoints.order(:name).to_a
if http_endpoints.present? if http_endpoints.present?
s << "<optgroup label='HTTP Endpoints'>" s << "<optgroup label='HTTP Endpoints'>"
for endpoint in http_endpoints http_endpoints.each do |endpoint|
value = "#{endpoint.class}##{endpoint.uuid}" value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : "" selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>" s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>"
@@ -65,7 +67,7 @@ module ApplicationHelper
smtp_endpoints = server.smtp_endpoints.order(:name).to_a smtp_endpoints = server.smtp_endpoints.order(:name).to_a
if smtp_endpoints.present? if smtp_endpoints.present?
s << "<optgroup label='SMTP Endpoints'>" s << "<optgroup label='SMTP Endpoints'>"
for endpoint in smtp_endpoints smtp_endpoints.each do |endpoint|
value = "#{endpoint.class}##{endpoint.uuid}" value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : "" selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>" s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>"
@@ -76,7 +78,7 @@ module ApplicationHelper
address_endpoints = server.address_endpoints.order(:address).to_a address_endpoints = server.address_endpoints.order(:address).to_a
if address_endpoints.present? if address_endpoints.present?
s << "<optgroup label='Address Endpoints'>" s << "<optgroup label='Address Endpoints'>"
for endpoint in address_endpoints address_endpoints.each do |endpoint|
value = "#{endpoint.class}##{endpoint.uuid}" value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : "" selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.address}</option>" s << "<option value='#{value}' #{selected}>#{endpoint.address}</option>"
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ActionDeletionJob < Postal::Job class ActionDeletionJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ActionDeletionsJob < Postal::Job class ActionDeletionsJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class CheckAllDNSJob < Postal::Job class CheckAllDNSJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require "authie/session" require "authie/session"
class CleanupAuthieSessionsJob < Postal::Job class CleanupAuthieSessionsJob < Postal::Job
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ExpireHeldMessagesJob < Postal::Job class ExpireHeldMessagesJob < Postal::Job
def perform def perform
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ProcessMessageRetentionJob < Postal::Job class ProcessMessageRetentionJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class PruneSuppressionListsJob < Postal::Job class PruneSuppressionListsJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class PruneWebhookRequestsJob < Postal::Job class PruneWebhookRequestsJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class RequeueWebhooksJob < Postal::Job class RequeueWebhooksJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class SendNotificationsJob < Postal::Job class SendNotificationsJob < Postal::Job
def perform def perform
+8 -7
View File
@@ -1,16 +1,17 @@
# frozen_string_literal: true
class SendWebhookJob < Postal::Job class SendWebhookJob < Postal::Job
def perform def perform
if server = Server.find(params["server_id"]) if server = Server.find(params["server_id"])
new_items = {} new_items = {}
if params["payload"] params["payload"]&.each do |key, value|
for key, value in params["payload"] next unless key.to_s =~ /\A_(\w+)/
next unless key.to_s =~ /\A_(\w+)/
begin begin
new_items[::Regexp.last_match(1)] = server.message_db.message(value.to_i).webhook_hash new_items[::Regexp.last_match(1)] = server.message_db.message(value.to_i).webhook_hash
rescue Postal::MessageDB::Message::NotFound rescue Postal::MessageDB::Message::NotFound
end # No message found, don't do any replacement
end end
end end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class SleepJob < Postal::Job class SleepJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class TidyRawMessagesJob < Postal::Job class TidyRawMessagesJob < Postal::Job
def perform def perform
+17 -9
View File
@@ -1,5 +1,8 @@
# frozen_string_literal: true
class UnqueueMessageJob < Postal::Job class UnqueueMessageJob < Postal::Job
# rubocop:disable Layout/LineLength
def perform def perform
if original_message = QueuedMessage.find_by_id(params["id"]) if original_message = QueuedMessage.find_by_id(params["id"])
if original_message.acquire_lock if original_message.acquire_lock
@@ -91,11 +94,11 @@ class UnqueueMessageJob < Postal::Job
log "#{log_prefix} Message is a bounce" log "#{log_prefix} Message is a bounce"
original_messages = queued_message.message.original_messages original_messages = queued_message.message.original_messages
unless original_messages.empty? unless original_messages.empty?
for original_message in queued_message.message.original_messages queued_message.message.original_messages.each do |orig_msg|
queued_message.message.update(bounce_for_id: original_message.id, domain_id: original_message.domain_id) queued_message.message.update(bounce_for_id: orig_msg.id, domain_id: orig_msg.domain_id)
queued_message.message.create_delivery("Processed", details: "This has been detected as a bounce message for <msg:#{original_message.id}>.") queued_message.message.create_delivery("Processed", details: "This has been detected as a bounce message for <msg:#{orig_msg.id}>.")
original_message.bounce!(queued_message.message) orig_msg.bounce!(queued_message.message)
log "#{log_prefix} Bounce linked with message #{original_message.id}" log "#{log_prefix} Bounce linked with message #{orig_msg.id}"
end end
queued_message.destroy queued_message.destroy
next next
@@ -421,15 +424,17 @@ class UnqueueMessageJob < Postal::Job
end end
rescue StandardError => e rescue StandardError => e
log "#{log_prefix} Internal error: #{e.class}: #{e.message}" log "#{log_prefix} Internal error: #{e.class}: #{e.message}"
e.backtrace.each { |e| log("#{log_prefix} #{e}") } e.backtrace.each { |line| log("#{log_prefix} #{line}") }
queued_message.retry_later queued_message.retry_later
log "#{log_prefix} Queued message was unlocked" log "#{log_prefix} Queued message was unlocked"
if defined?(Sentry) if defined?(Sentry)
Sentry.capture_exception(e, extra: { job_id: self.id, server_id: queued_message.server_id, message_id: queued_message.message_id }) Sentry.capture_exception(e, extra: { job_id: self.id, server_id: queued_message.server_id, message_id: queued_message.message_id })
end end
if queued_message.message queued_message.message&.create_delivery("Error",
queued_message.message.create_delivery("Error", details: "An internal error occurred while sending this message. This message will be retried automatically. If this persists, contact support for assistance.", output: "#{e.class}: #{e.message}", log_id: "J-#{self.id}") details: "An internal error occurred while sending " \
end "this message. This message will be retried " \
"automatically.",
output: "#{e.class}: #{e.message}", log_id: "J-#{self.id}")
end end
end end
@@ -446,9 +451,11 @@ class UnqueueMessageJob < Postal::Job
nil nil
end end
end end
# rubocop:enable Layout/LineLength
private private
# rubocop:disable Naming/MemoizedInstanceVariableName
def cached_sender(klass, *args) def cached_sender(klass, *args)
@sender ||= begin @sender ||= begin
sender = klass.new(*args) sender = klass.new(*args)
@@ -456,5 +463,6 @@ class UnqueueMessageJob < Postal::Job
sender sender
end end
end end
# rubocop:enable Naming/MemoizedInstanceVariableName
end end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class WebhookDeliveryJob < Postal::Job class WebhookDeliveryJob < Postal::Job
def perform def perform
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class AppMailer < ApplicationMailer class AppMailer < ApplicationMailer
def verify_email_address(user) def verify_email_address(user)
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default from: "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>" default from: "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>"
+3 -3
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: additional_route_endpoints # Table name: additional_route_endpoints
@@ -35,9 +37,7 @@ class AdditionalRouteEndpoint < ApplicationRecord
end end
def _endpoint=(value) def _endpoint=(value)
if value.blank? if value && value =~ /\#/
self.endpoint = nil
elsif value =~ /\#/
class_name, id = value.split("#", 2) class_name, id = value.split("#", 2)
unless Route::ENDPOINT_TYPES.include?(class_name) unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'" raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: address_endpoints # Table name: address_endpoints
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true self.abstract_class = true
+2 -14
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module HasAuthentication module HasAuthentication
extend ActiveSupport::Concern extend ActiveSupport::Concern
@@ -42,18 +44,4 @@ module HasAuthentication
end end
class Postal::Errors::AuthenticationError < Postal::Error
attr_reader :error
def initialize(error)
@error = error
end
def to_s
"Authentication Failed: #{@error}"
end
end
# -*- SkipSchemaAnnotations # -*- SkipSchemaAnnotations
+5 -3
View File
@@ -1,9 +1,11 @@
# frozen_string_literal: true
require "resolv" require "resolv"
module HasDNSChecks module HasDNSChecks
def dns_ok? def dns_ok?
spf_status == "OK" && dkim_status == "OK" && ["OK", "Missing"].include?(mx_status) && ["OK", "Missing"].include?(return_path_status) spf_status == "OK" && dkim_status == "OK" && %w[OK Missing].include?(mx_status) && %w[OK Missing].include?(return_path_status)
end end
def dns_checked? def dns_checked?
@@ -42,12 +44,12 @@ module HasDNSChecks
def check_spf_record def check_spf_record
result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT) result = resolver.getresources(name, Resolv::DNS::Resource::IN::TXT)
spf_records = result.map(&:data).select { |d| d =~ /\Av=spf1/ } spf_records = result.map(&:data).grep(/\Av=spf1/)
if spf_records.empty? if spf_records.empty?
self.spf_status = "Missing" self.spf_status = "Missing"
self.spf_error = "No SPF record exists for this domain" self.spf_error = "No SPF record exists for this domain"
else else
suitable_spf_records = spf_records.select { |d| d =~ /include:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/ } suitable_spf_records = spf_records.grep(/include:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/)
if suitable_spf_records.empty? if suitable_spf_records.empty?
self.spf_status = "Invalid" self.spf_status = "Invalid"
self.spf_error = "An SPF record exists but it doesn't include #{Postal.config.dns.spf_include}" self.spf_error = "An SPF record exists but it doesn't include #{Postal.config.dns.spf_include}"
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module HasMessage module HasMessage
def self.included(base) def self.included(base)
@@ -18,7 +20,7 @@ module HasMessage
def include_message def include_message
queued_messages = all.to_a queued_messages = all.to_a
server_ids = queued_messages.map(&:server_id).uniq server_ids = queued_messages.map(&:server_id).uniq
if server_ids.size == 0 if server_ids.empty?
return [] return []
elsif server_ids.size > 1 elsif server_ids.size > 1
raise Postal::Error, "'include_message' can only be used on collections of messages from the same server" raise Postal::Error, "'include_message' can only be used on collections of messages from the same server"
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module HasSoftDestroy module HasSoftDestroy
def self.included(base) def self.included(base)
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module HasUUID module HasUUID
def self.included(base) def self.included(base)
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: credentials # Table name: credentials
@@ -21,7 +23,7 @@ class Credential < ApplicationRecord
belongs_to :server belongs_to :server
TYPES = ["SMTP", "API", "SMTP-IP"] TYPES = %w[SMTP API SMTP-IP].freeze
validates :key, presence: true, uniqueness: { case_sensitive: false } validates :key, presence: true, uniqueness: { case_sensitive: false }
validates :type, inclusion: { in: TYPES } validates :type, inclusion: { in: TYPES }
+6 -5
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: domains # Table name: domains
@@ -42,15 +44,14 @@ class Domain < ApplicationRecord
include HasDNSChecks include HasDNSChecks
VERIFICATION_EMAIL_ALIASES = ["webmaster", "postmaster", "admin", "administrator", "hostmaster"] VERIFICATION_EMAIL_ALIASES = %w[webmaster postmaster admin administrator hostmaster].freeze
VERIFICATION_METHODS = %w[DNS Email].freeze
belongs_to :server, optional: true belongs_to :server, optional: true
belongs_to :owner, optional: true, polymorphic: true belongs_to :owner, optional: true, polymorphic: true
has_many :routes, dependent: :destroy has_many :routes, dependent: :destroy
has_many :track_domains, dependent: :destroy has_many :track_domains, dependent: :destroy
VERIFICATION_METHODS = ["DNS", "Email"]
validates :name, presence: true, format: { with: /\A[a-z0-9\-.]*\z/ }, uniqueness: { case_sensitive: false, scope: [:owner_type, :owner_id], message: "is already added" } validates :name, presence: true, format: { with: /\A[a-z0-9\-.]*\z/ }, uniqueness: { case_sensitive: false, scope: [:owner_type, :owner_id], message: "is already added" }
validates :verification_method, inclusion: { in: VERIFICATION_METHODS } validates :verification_method, inclusion: { in: VERIFICATION_METHODS }
@@ -83,8 +84,8 @@ class Domain < ApplicationRecord
def parent_domains def parent_domains
parts = name.split(".") parts = name.split(".")
parts[0, parts.size - 1].each_with_index.map do |p, i| parts[0, parts.size - 1].each_with_index.map do |_, i|
parts[i..-1].join(".") parts[i..].join(".")
end end
end end
+4 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: http_endpoints # Table name: http_endpoints
@@ -29,8 +31,8 @@ class HTTPEndpoint < ApplicationRecord
has_many :routes, as: :endpoint has_many :routes, as: :endpoint
has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint
ENCODINGS = ["BodyAsJSON", "FormData"] ENCODINGS = %w[BodyAsJSON FormData].freeze
FORMATS = ["Hash", "RawMessage"] FORMATS = %w[Hash RawMessage].freeze
before_destroy :update_routes before_destroy :update_routes
+9 -5
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
class IncomingMessagePrototype class IncomingMessagePrototype
attr_accessor :to attr_accessor :to
@@ -22,13 +24,14 @@ class IncomingMessagePrototype
end end
def route def route
@routes ||= if @to.present? @route ||= if @to.present?
uname, domain = @to.split("@", 2) uname, domain = @to.split("@", 2)
uname, tag = uname.split("+", 2) uname, _tag = uname.split("+", 2)
@server.routes.includes(:domain).where(domains: { name: domain }, name: uname).first @server.routes.includes(:domain).where(domains: { name: domain }, name: uname).first
end end
end end
# rubocop:disable Lint/DuplicateMethods
def attachments def attachments
(@attachments || []).map do |attachment| (@attachments || []).map do |attachment|
{ {
@@ -38,6 +41,7 @@ class IncomingMessagePrototype
} }
end end
end end
# rubocop:enable Lint/DuplicateMethods
def create_messages def create_messages
if valid? if valid?
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: ip_addresses # Table name: ip_addresses
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: ip_pools # Table name: ip_pools
+17 -11
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: ip_pool_rules # Table name: ip_pool_rules
@@ -66,18 +68,22 @@ class IPPoolRule < ApplicationRecord
errors.add :ip_pool_id, "must belong to the organization" errors.add :ip_pool_id, "must belong to the organization"
end end
def self.address_matches?(condition, address) class << self
address = Postal::Helpers.strip_name_from_address(address)
if condition =~ /@/ def address_matches?(condition, address)
parts = address.split("@") address = Postal::Helpers.strip_name_from_address(address)
domain = parts.pop if condition =~ /@/
uname = parts.join("@") parts = address.split("@")
uname, = uname.split("+", 2) domain = parts.pop
condition == "#{uname}@#{domain}" uname = parts.join("@")
else uname, = uname.split("+", 2)
# Match as a domain condition == "#{uname}@#{domain}"
condition == address.split("@").last else
# Match as a domain
condition == address.split("@").last
end
end end
end end
end end
+4 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: organizations # Table name: organizations
@@ -23,7 +25,7 @@
class Organization < ApplicationRecord class Organization < ApplicationRecord
RESERVED_PERMALINKS = ["new", "edit", "remove", "delete", "destroy", "admin", "mail", "org", "server"] RESERVED_PERMALINKS = %w[new edit remove delete destroy admin mail org server].freeze
INITIAL_QUOTA = 10 INITIAL_QUOTA = 10
INITIAL_SUPER_QUOTA = 10_000 INITIAL_SUPER_QUOTA = 10_000
@@ -48,7 +50,7 @@ class Organization < ApplicationRecord
has_many :ip_pool_rules, dependent: :destroy, as: :owner has_many :ip_pool_rules, dependent: :destroy, as: :owner
after_create do after_create do
if pool = IPPool.default if IPPool.default
ip_pools << IPPool.default ip_pools << IPPool.default
end end
end end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: organization_ip_pools # Table name: organization_ip_pools
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: organization_users # Table name: organization_users
+9 -7
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require "resolv" require "resolv"
class OutgoingMessagePrototype class OutgoingMessagePrototype
@@ -47,13 +49,11 @@ class OutgoingMessagePrototype
end end
def find_domain def find_domain
@domain ||= begin domain = @server.authenticated_domain_for_address(@from)
domain = @server.authenticated_domain_for_address(@from) if @server.allow_sender? && domain.nil?
if @server.allow_sender? && domain.nil? domain = @server.authenticated_domain_for_address(@sender)
domain = @server.authenticated_domain_for_address(@sender)
end
domain || :none
end end
domain || :none
end end
def to_addresses def to_addresses
@@ -93,6 +93,7 @@ class OutgoingMessagePrototype
@errors || {} @errors || {}
end end
# rubocop:disable Lint/DuplicateMethods
def attachments def attachments
(@attachments || []).map do |attachment| (@attachments || []).map do |attachment|
{ {
@@ -102,6 +103,7 @@ class OutgoingMessagePrototype
} }
end end
end end
# rubocop:enable Lint/DuplicateMethods
def validate def validate
@errors = [] @errors = []
@@ -135,7 +137,7 @@ class OutgoingMessagePrototype
end end
if attachments.present? if attachments.present?
attachments.each_with_index do |attachment, index| attachments.each do |attachment|
if attachment[:name].blank? if attachment[:name].blank?
@errors << "AttachmentMissingName" unless @errors.include?("AttachmentMissingName") @errors << "AttachmentMissingName" unless @errors.include?("AttachmentMissingName")
elsif attachment[:data].blank? elsif attachment[:data].blank?
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: queued_messages # Table name: queued_messages
+16 -11
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: routes # Table name: routes
@@ -22,7 +24,9 @@
class Route < ApplicationRecord class Route < ApplicationRecord
MODES = ["Endpoint", "Accept", "Hold", "Bounce", "Reject"] MODES = %w[Endpoint Accept Hold Bounce Reject].freeze
SPAM_MODES = %w[Mark Quarantine Fail].freeze
ENDPOINT_TYPES = %w[SMTPEndpoint HTTPEndpoint AddressEndpoint].freeze
include HasUUID include HasUUID
@@ -31,9 +35,6 @@ class Route < ApplicationRecord
belongs_to :endpoint, polymorphic: true, optional: true belongs_to :endpoint, polymorphic: true, optional: true
has_many :additional_route_endpoints, dependent: :destroy has_many :additional_route_endpoints, dependent: :destroy
SPAM_MODES = ["Mark", "Quarantine", "Fail"]
ENDPOINT_TYPES = ["SMTPEndpoint", "HTTPEndpoint", "AddressEndpoint"]
validates :name, presence: true, format: /\A(([a-z0-9\-.]*)|(\*)|(__returnpath__))\z/ validates :name, presence: true, format: /\A(([a-z0-9\-.]*)|(\*)|(__returnpath__))\z/
validates :spam_mode, inclusion: { in: SPAM_MODES } validates :spam_mode, inclusion: { in: SPAM_MODES }
validates :endpoint, presence: { if: proc { mode == "Endpoint" } } validates :endpoint, presence: { if: proc { mode == "Endpoint" } }
@@ -115,7 +116,7 @@ class Route < ApplicationRecord
if route.save if route.save
seen << route.id seen << route.id
else else
route.errors.each do |field, message| route.errors.each do |_, message|
errors.add :base, message errors.add :base, message
end end
raise ActiveRecord::RecordInvalid raise ActiveRecord::RecordInvalid
@@ -200,7 +201,7 @@ class Route < ApplicationRecord
if route = Route.includes(:domain).where(domains: { name: domain.name }, name: name).where.not(id: id).first if route = Route.includes(:domain).where(domains: { name: domain.name }, name: name).where.not(id: id).first
errors.add :name, "is configured on the #{route.server.full_permalink} mail server" errors.add :name, "is configured on the #{route.server.full_permalink} mail server"
end end
elsif route = Route.where(name: "__returnpath__").where.not(id: id).exists? elsif Route.where(name: "__returnpath__").where.not(id: id).exists?
errors.add :base, "A return path route already exists for this server" errors.add :base, "A return path route already exists for this server"
end end
end end
@@ -218,12 +219,16 @@ class Route < ApplicationRecord
errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint" errors.add :base, "Additional routes are not permitted unless the primary route is an actual endpoint"
end end
def self.find_by_name_and_domain(name, domain) class << self
route = Route.includes(:domain).where(name: name, domains: { name: domain }).first
if route.nil? def find_by_name_and_domain(name, domain)
route = Route.includes(:domain).where(name: "*", domains: { name: domain }).first route = Route.includes(:domain).where(name: name, domains: { name: domain }).first
if route.nil?
route = Route.includes(:domain).where(name: "*", domains: { name: domain }).first
end
route
end end
route
end end
end end
+25 -12
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: servers # Table name: servers
@@ -41,7 +43,8 @@
class Server < ApplicationRecord class Server < ApplicationRecord
RESERVED_PERMALINKS = ["new", "all", "search", "stats", "edit", "manage", "delete", "destroy", "remove"] RESERVED_PERMALINKS = %w[new all search stats edit manage delete destroy remove].freeze
MODES = %w[Live Development].freeze
include HasUUID include HasUUID
include HasSoftDestroy include HasSoftDestroy
@@ -62,8 +65,6 @@ class Server < ApplicationRecord
has_many :track_domains, dependent: :destroy has_many :track_domains, dependent: :destroy
has_many :ip_pool_rules, dependent: :destroy, as: :owner has_many :ip_pool_rules, dependent: :destroy, as: :owner
MODES = ["Live", "Development"]
random_string :token, type: :chars, length: 6, unique: true, upper_letters_only: true random_string :token, type: :chars, length: 6, unique: true, upper_letters_only: true
default_value :permalink, -> { name ? name.parameterize : nil } default_value :permalink, -> { name ? name.parameterize : nil }
default_value :raw_message_retention_days, -> { 30 } default_value :raw_message_retention_days, -> { 30 }
@@ -155,11 +156,11 @@ class Server < ApplicationRecord
time = Time.now.utc time = Time.now.utc
total_outgoing = 0.0 total_outgoing = 0.0
total_bounces = 0.0 total_bounces = 0.0
message_db.statistics.get(:daily, [:outgoing, :bounces], time, 30).each do |date, stat| message_db.statistics.get(:daily, [:outgoing, :bounces], time, 30).each do |_, stat|
total_outgoing += stat[:outgoing] total_outgoing += stat[:outgoing]
total_bounces += stat[:bounces] total_bounces += stat[:bounces]
end end
total_outgoing == 0 ? 0 : (total_bounces / total_outgoing) * 100 total_outgoing.zero? ? 0 : (total_bounces / total_outgoing) * 100
end end
end end
@@ -218,6 +219,10 @@ class Server < ApplicationRecord
} }
end end
# Return the domain which can be used to authenticate emails sent from the given e-mail address.
#
#  @param address [String] an e-mail address
# @return [Domain, nil] the domain to use for authentication
def authenticated_domain_for_address(address) def authenticated_domain_for_address(address)
return nil if address.blank? return nil if address.blank?
@@ -226,16 +231,24 @@ class Server < ApplicationRecord
return nil unless uname return nil unless uname
return nil unless domain_name return nil unless domain_name
uname, = uname.split("+", 2) # Find a verified domain which directly matches the domain name for the given address.
domain = Domain.verified
.order(owner_type: :desc)
.where("(owner_type = 'Organization' AND owner_id = ?) OR " \
"(owner_type = 'Server' AND owner_id = ?)", organization_id, id)
.where(name: domain_name)
.first
# Check the server's domain # If there is a matching domain, return it
if domain = Domain.verified.order(owner_type: :desc).where("(owner_type = 'Organization' AND owner_id = ?) OR (owner_type = 'Server' AND owner_id = ?)", organization_id, id).where(name: domain_name).first return domain if domain
return domain
end
return unless any_domain = domains.verified.where(use_for_any: true).order(:name).first # Otherwise, we need to look to see if there is a domain configured which can be used as the authenticated
# domain for any domain. This will look for domains directly within the server and return that.
any_domain = domains.verified.where(use_for_any: true).order(:name).first
return any_domain if any_domain
any_domain # Return nil if we can't find anything suitable
nil
end end
def find_authenticated_domain_from_headers(headers) def find_authenticated_domain_from_headers(headers)
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: smtp_endpoints # Table name: smtp_endpoints
@@ -24,7 +26,7 @@ class SMTPEndpoint < ApplicationRecord
has_many :routes, as: :endpoint has_many :routes, as: :endpoint
has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint has_many :additional_route_endpoints, dependent: :destroy, as: :endpoint
SSL_MODES = ["None", "Auto", "STARTTLS", "TLS"] SSL_MODES = %w[None Auto STARTTLS TLS].freeze
before_destroy :update_routes before_destroy :update_routes
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: statistics # Table name: statistics
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: track_domains # Table name: track_domains
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: users # Table name: users
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: user_invites # Table name: user_invites
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: webhooks # Table name: webhooks
+12 -10
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: webhook_events # Table name: webhook_events
@@ -14,16 +16,16 @@
class WebhookEvent < ApplicationRecord class WebhookEvent < ApplicationRecord
EVENTS = [ EVENTS = %w[
"MessageSent", MessageSent
"MessageDelayed", MessageDelayed
"MessageDeliveryFailed", MessageDeliveryFailed
"MessageHeld", MessageHeld
"MessageBounced", MessageBounced
"MessageLinkClicked", MessageLinkClicked
"MessageLoaded", MessageLoaded
"DomainDNSError" DomainDNSError
] ].freeze
belongs_to :webhook belongs_to :webhook
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: webhook_requests # Table name: webhook_requests
@@ -19,7 +21,7 @@ class WebhookRequest < ApplicationRecord
include HasUUID include HasUUID
RETRIES = { 1 => 2.minutes, 2 => 3.minutes, 3 => 6.minutes, 4 => 10.minutes, 5 => 15.minutes } RETRIES = { 1 => 2.minutes, 2 => 3.minutes, 3 => 6.minutes, 4 => 10.minutes, 5 => 15.minutes }.freeze
belongs_to :server belongs_to :server
belongs_to :webhook, optional: true belongs_to :webhook, optional: true
+2
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
APP_PATH = File.expand_path("../config/application", __dir__) APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot" require_relative "../config/boot"
require "rails/commands" require "rails/commands"
+2
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require_relative "../config/boot" require_relative "../config/boot"
require "rake" require "rake"
Rake.application.run Rake.application.run
+2
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require "pathname" require "pathname"
require "fileutils" require "fileutils"
include FileUtils include FileUtils
+2
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
require "pathname" require "pathname"
require "fileutils" require "fileutils"
include FileUtils include FileUtils
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# This file is used by Rack-based servers to start the application. # This file is used by Rack-based servers to start the application.
require_relative "config/environment" require_relative "config/environment"
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative "boot" require_relative "boot"
require "rails" require "rails"
@@ -30,7 +32,7 @@ module Postal
config.eager_load_paths << Rails.root.join("lib") config.eager_load_paths << Rails.root.join("lib")
# Disable field_with_errors # Disable field_with_errors
config.action_view.field_error_proc = proc { |t, i| t } config.action_view.field_error_proc = proc { |t, _| t }
# Load the tracking server middleware # Load the tracking server middleware
require "postal/tracking_middleware" require "postal/tracking_middleware"
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
$stdout.sync = true $stdout.sync = true
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Clockwork module Clockwork
configure do |config| configure do |config|
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Load the Rails application. # Load the Rails application.
require_relative "application" require_relative "application"
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
+3 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
@@ -71,7 +73,7 @@ Rails.application.configure do
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV["RAILS_LOG_TO_STDOUT"].present? if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT) logger = ActiveSupport::Logger.new($stdout)
logger.formatter = config.log_formatter logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger) config.logger = ActiveSupport::TaggedLogging.new(logger)
end end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
@@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# ApplicationController.renderer.defaults.merge!( # ApplicationController.renderer.defaults.merge!(
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.
@@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Specify a serializer for the signed and encrypted cookie jars. # Specify a serializer for the signed and encrypted cookie jars.
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file. # Configure sensitive parameters which will be filtered from the log file.
+2 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections # Add new inflection rules using the following format. Inflections
@@ -12,7 +14,6 @@
# These inflection rules are supported but not enabled by default: # These inflection rules are supported but not enabled by default:
ActiveSupport::Inflector.inflections(:en) do |inflect| ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "DKIM" inflect.acronym "DKIM"
inflect.acronym "HTTP" inflect.acronym "HTTP"
inflect.acronym "SMTP" inflect.acronym "SMTP"
+10 -6
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require "mail" require "mail"
module Mail module Mail
@@ -20,7 +22,7 @@ module Mail
## Extract plain text body of message ## Extract plain text body of message
def plain_body def plain_body
if multipart? and text_part if multipart? && text_part
text_part.decoded text_part.decoded
elsif mime_type == "text/plain" || mime_type.nil? elsif mime_type == "text/plain" || mime_type.nil?
decoded decoded
@@ -29,7 +31,7 @@ module Mail
## Extract HTML text body of message ## Extract HTML text body of message
def html_body def html_body
if multipart? and html_part if multipart? && html_part
html_part.decoded html_part.decoded
elsif mime_type == "text/html" elsif mime_type == "text/html"
decoded decoded
@@ -109,14 +111,16 @@ module Mail
# Handle attached emails as attachments # Handle attached emails as attachments
class AttachmentsList < Array class AttachmentsList < Array
# rubocop:disable Lint/MissingSuper
def initialize(parts_list) def initialize(parts_list)
@parts_list = parts_list @parts_list = parts_list
@content_disposition_type = "attachment" @content_disposition_type = "attachment"
parts_list.map do |p| parts = parts_list.map do |p|
(p.parts.empty? and p.attachment?) ? p : p.attachments p.parts.empty? && p.attachment? ? p : p.attachments
end.flatten.compact.each { |a| self << a } end.flatten.compact
self parts.each { |a| self << a }
end end
# rubocop:enable Lint/MissingSuper
end end
+1
View File
@@ -1,3 +1,4 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks: # Add new mime types for use in respond_to blocks:
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# #
# This file contains migration options to ease your Rails 5.0 upgrade. # This file contains migration options to ease your Rails 5.0 upgrade.
+2
View File
@@ -1 +1,3 @@
# frozen_string_literal: true
require "postal" require "postal"
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module ActionView module ActionView
module RecordIdentifier module RecordIdentifier

Some files were not shown because too many files have changed in this diff Show More