From d2c0c366cbb417a97be47b6ed77525094156bbbb Mon Sep 17 00:00:00 2001 From: Ricardo Pacheco Date: Fri, 23 Feb 2024 16:31:03 -0300 Subject: [PATCH] [AF-19] Add service layer (#20) * Add service layer * Add testing and minor configurations * Configure environment variables and update lib version * Update CHANGELOG and fix layout main template for emails * Upgrade lib version and update README --- .env.development.template | 3 + .env.test.template | 3 + .tool-versions | 1 + CHANGELOG.md | 16 ++ Gemfile | 1 + Gemfile.lock | 22 ++- Procfile | 1 + README.md | 4 + config/application.rb | 1 + config/boot.rb | 4 +- .../en-US/contracts/contracts.en-US.yml | 0 .../mail/user_context/registration.en-US.yml | 11 ++ .../pt-BR/contracts/contracts.pt-BR.yml | 0 .../mail/user_context/registration.pt-BR.yml | 11 ++ lib/auction_fun_core.rb | 4 + .../contracts/application_contract.rb | 2 +- .../services/mail/templates/layout.html.erb | 72 ++++++++ .../user_context/registration.html.erb | 168 ++++++++++++++++++ .../mail/user_context/registration_mailer.rb | 25 +++ lib/auction_fun_core/version.rb | 2 +- .../user_context/registration_mailer_spec.rb | 33 ++++ spec/auction_fun_core_spec.rb | 2 +- spec/support/mail.rb | 13 ++ system/providers/core.rb | 1 + system/providers/mail.rb | 25 +++ system/providers/settings.rb | 6 + 26 files changed, 426 insertions(+), 5 deletions(-) rename config/locales/contracts/en-US.yml => i18n/en-US/contracts/contracts.en-US.yml (100%) create mode 100644 i18n/en-US/mail/user_context/registration.en-US.yml rename config/locales/contracts/pt-BR.yml => i18n/pt-BR/contracts/contracts.pt-BR.yml (100%) create mode 100644 i18n/pt-BR/mail/user_context/registration.pt-BR.yml create mode 100644 lib/auction_fun_core/services/mail/templates/layout.html.erb create mode 100644 lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb create mode 100644 lib/auction_fun_core/services/mail/user_context/registration_mailer.rb create mode 100644 spec/auction_fun_core/services/mail/user_context/registration_mailer_spec.rb create mode 100644 spec/support/mail.rb create mode 100644 system/providers/mail.rb diff --git a/.env.development.template b/.env.development.template index eba075b..f66eabd 100644 --- a/.env.development.template +++ b/.env.development.template @@ -1,2 +1,5 @@ APP_ENV=development DATABASE_URL=postgresql://postgres:@127.0.0.1/auction_fun_core_development?pool=10 +DEFAULT_EMAIL_SYSTEM=system@auctionfun.app +SMTP_ADDRESS=localhost +SMTP_PORT=1025 diff --git a/.env.test.template b/.env.test.template index ef44236..50b2177 100644 --- a/.env.test.template +++ b/.env.test.template @@ -1,2 +1,5 @@ APP_ENV=test DATABASE_URL=postgresql://postgres:@127.0.0.1:5432/auction_fun_core_test?pool=10 +DEFAULT_EMAIL_SYSTEM=system@auctionfun.app +SMTP_ADDRESS=localhost +SMTP_PORT=1025 diff --git a/.tool-versions b/.tool-versions index 8c8a437..d79ebeb 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,4 @@ ruby 3.3.0 postgres 16.1 golang 1.21.5 +nodejs 20.10.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 427f757..1b0e7b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ ## [Unreleased] +## [0.5.0] - 2024-02-23 + +### Added + +- Layer for external services. +- Creation of first external service: email. +- Configure email as application provider. +- Mandatory environment variables for communication with email service. +- Configuring development environment to run email service on local machine. +- Using `idlemailer` dependency to build emails and triggers. + +## Changes + +- I18n locale directory from `config/locales` to root path of project. +- Scope i18n messages by locale. + ## [0.4.1] - 2024-02-20 ### Added diff --git a/Gemfile b/Gemfile index ca1daec..66507df 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ gem "dry-matcher", "1.0.0" gem "dry-monads", "1.6.0" gem "dry-system", "1.0.1" gem "dry-validation", "1.10.0" +gem "idlemailer", "2.2.0" gem "money", "6.16.0" gem "pg", "1.5.5" gem "phonelib", "0.8.7" diff --git a/Gemfile.lock b/Gemfile.lock index d0529e4..581e7c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - auction_fun_core (0.4.1) + auction_fun_core (0.5.0) GEM remote: https://rubygems.org/ @@ -27,6 +27,7 @@ GEM database_cleaner-sequel (2.0.2) database_cleaner-core (~> 2.0.0) sequel + date (3.3.4) diff-lcs (1.5.1) docile (1.4.0) dotenv (3.0.2) @@ -92,14 +93,31 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) ice_nine (0.11.2) + idlemailer (2.2.0) + mail (~> 2.0) json (2.7.1) language_server-protocol (3.17.0.3) lint_roller (1.1.0) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp method_source (1.0.0) + mini_mime (1.1.5) minitest (5.22.2) money (6.16.0) i18n (>= 0.6.4, <= 2) mutex_m (0.2.0) + net-imap (0.4.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.4.0.1) + net-protocol parallel (1.24.0) parser (3.3.0.5) ast (~> 2.4.1) @@ -197,6 +215,7 @@ GEM standard-performance (1.3.1) lint_roller (~> 1.1) rubocop-performance (~> 1.20.2) + timeout (0.4.1) transproc (1.1.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -220,6 +239,7 @@ DEPENDENCIES dry-system (= 1.0.1) dry-validation (= 1.10.0) faker (= 3.2.3) + idlemailer (= 2.2.0) money (= 6.16.0) pg (= 1.5.5) phonelib (= 0.8.7) diff --git a/Procfile b/Procfile index c0dbddd..abb787a 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ database: postgres +mailserver: maildev --hide-extensions STARTTLS diff --git a/README.md b/README.md index 220216a..23e599e 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,10 @@ This project uses `yadr` as a documentation tool. To generate documentation and Documentation will be available at `http://localhost:8808` +## External resources + +- [Email templates](https://codedmails.com/) + ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/config/application.rb b/config/application.rb index 62e27d2..b52d59f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -13,6 +13,7 @@ module AuctionFunCore # Main class (Add doc) class Application < Dry::System::Container + I18n.load_path += Dir[File.expand_path("i18n/**/*.{rb,yml}")] I18n.available_locales = %w[en-US pt-BR] I18n.default_locale = "pt-BR" use :env, inferrer: -> { ENV.fetch("APP_ENV", "development").to_sym } diff --git a/config/boot.rb b/config/boot.rb index 2af1743..501ac86 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -8,5 +8,7 @@ unless defined?(Dotenv) require "dotenv" Dotenv.load(".env.#{ENV.fetch("APP_ENV", nil)}") - Dotenv.require_keys("DATABASE_URL") + Dotenv.require_keys( + "DATABASE_URL", "DEFAULT_EMAIL_SYSTEM", "SMTP_ADDRESS", "SMTP_PORT" + ) end diff --git a/config/locales/contracts/en-US.yml b/i18n/en-US/contracts/contracts.en-US.yml similarity index 100% rename from config/locales/contracts/en-US.yml rename to i18n/en-US/contracts/contracts.en-US.yml diff --git a/i18n/en-US/mail/user_context/registration.en-US.yml b/i18n/en-US/mail/user_context/registration.en-US.yml new file mode 100644 index 0000000..6683cbf --- /dev/null +++ b/i18n/en-US/mail/user_context/registration.en-US.yml @@ -0,0 +1,11 @@ +en-US: + mail: + general: + hello: "Hi %{name}" + app_name: "AuctionFun" + team: "Team AuctionFun" + user_context: + registration: + subject: "Welcome to AuctionFun" + body: + description: "Welcome to our platform! We are delighted to have you on board. If you have any questions or need assistance, feel free to reach out." diff --git a/config/locales/contracts/pt-BR.yml b/i18n/pt-BR/contracts/contracts.pt-BR.yml similarity index 100% rename from config/locales/contracts/pt-BR.yml rename to i18n/pt-BR/contracts/contracts.pt-BR.yml diff --git a/i18n/pt-BR/mail/user_context/registration.pt-BR.yml b/i18n/pt-BR/mail/user_context/registration.pt-BR.yml new file mode 100644 index 0000000..2c5caef --- /dev/null +++ b/i18n/pt-BR/mail/user_context/registration.pt-BR.yml @@ -0,0 +1,11 @@ +pt-BR: + mail: + general: + hello: "Olá %{name}" + app_name: "AuctionFun" + team: "Time AuctionFun" + user_context: + registration: + subject: "Bem vindo a AuctionFun" + body: + description: "Bem-vindo/a à nossa plataforma! Estamos encantados por tê-lo/a conosco. Se tiver alguma dúvida ou precisar de ajuda, não hesite em entrar em contato." diff --git a/lib/auction_fun_core.rb b/lib/auction_fun_core.rb index 2a63167..511bcbd 100644 --- a/lib/auction_fun_core.rb +++ b/lib/auction_fun_core.rb @@ -13,5 +13,9 @@ def self.root File.expand_path "..", __dir__ end + def self.lib_path + File.expand_path ".", __dir__ + end + autoload :Application, Pathname.new(File.expand_path("../config/application", __dir__)) end diff --git a/lib/auction_fun_core/contracts/application_contract.rb b/lib/auction_fun_core/contracts/application_contract.rb index b25c61d..5de9f8f 100644 --- a/lib/auction_fun_core/contracts/application_contract.rb +++ b/lib/auction_fun_core/contracts/application_contract.rb @@ -17,7 +17,7 @@ class ApplicationContract < Dry::Validation::Contract config.messages.backend = :i18n config.messages.default_locale = I18n.default_locale config.messages.top_namespace = "contracts" - config.messages.load_paths << Application.root.join("config/locales/contracts/#{I18n.default_locale}.yml").to_s + config.messages.load_paths << Application.root.join("i18n/#{I18n.default_locale}/contracts/contracts.#{I18n.default_locale}.yml").to_s register_macro(:email_format) do next if EMAIL_REGEX.match?(value) diff --git a/lib/auction_fun_core/services/mail/templates/layout.html.erb b/lib/auction_fun_core/services/mail/templates/layout.html.erb new file mode 100644 index 0000000..0a886b8 --- /dev/null +++ b/lib/auction_fun_core/services/mail/templates/layout.html.erb @@ -0,0 +1,72 @@ + + + + AuctionFun + + + + + + + + + + + <%= yield %> + + + diff --git a/lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb b/lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb new file mode 100644 index 0000000..66498fc --- /dev/null +++ b/lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb @@ -0,0 +1,168 @@ + + + + +
+
+ + + + + + +
+ + + + +
+
+ + + + + + + + + +
+ + + + + + +
+ image description +
+
+
+

+ <%= I18n.t("mail.general.hello", name: @user.name) %>, +

+
+
+
+
+
+
+
+ + + + + +
+
+ + + + + + +
+ + + + +
+
+ + + + + + + + + + + + +
+
+ <%= I18n.t("mail.user_context.registration.body.description") %> +
+
+
+ <%= I18n.t("mail.general.team") %> +
+
+ + + + + + +
+ + + + + + +
+ + + + + + +
+ + twitter-logo + +
+
+
+ + + + + + +
+ + + + + + +
+ + facebook-logo + +
+
+
+ + + + + + +
+ + + + + + +
+ + instagram-logo + +
+
+
+
+
+
+
+
+
diff --git a/lib/auction_fun_core/services/mail/user_context/registration_mailer.rb b/lib/auction_fun_core/services/mail/user_context/registration_mailer.rb new file mode 100644 index 0000000..065b289 --- /dev/null +++ b/lib/auction_fun_core/services/mail/user_context/registration_mailer.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module AuctionFunCore + module Services + module Mail + module UserContext + class RegistrationMailer + include IdleMailer::Mailer + include IdleMailer::TemplateManager + + # @param user [ROM::Struct::User] The user object + def initialize(user) + @user = user + mail.to = user.email + mail.subject = I18n.t("mail.user_context.registration.subject") + end + + def self.template_name + IdleMailer.config.templates.join("user_context/registration") + end + end + end + end + end +end diff --git a/lib/auction_fun_core/version.rb b/lib/auction_fun_core/version.rb index 42802e6..e94f811 100644 --- a/lib/auction_fun_core/version.rb +++ b/lib/auction_fun_core/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module AuctionFunCore - VERSION = "0.4.1" + VERSION = "0.5.0" # Required class module is a gem dependency class Version; end diff --git a/spec/auction_fun_core/services/mail/user_context/registration_mailer_spec.rb b/spec/auction_fun_core/services/mail/user_context/registration_mailer_spec.rb new file mode 100644 index 0000000..1e5d919 --- /dev/null +++ b/spec/auction_fun_core/services/mail/user_context/registration_mailer_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe AuctionFunCore::Services::Mail::UserContext::RegistrationMailer, type: :mailer do + let(:default_email_system) { AuctionFunCore::Application[:settings].default_email_system } + + describe "#deliver" do + subject(:mailer) { described_class.new(user) } + + context "when user has invalid data" do + let(:user) { Factory.structs[:user, id: 1, email: nil] } + + it "expect raise error" do + expect { mailer.deliver }.to raise_error( + ArgumentError, "SMTP To address may not be blank: []" + ) + end + end + + context "when user has valid data" do + let(:user) { Factory.structs[:user, id: 1] } + + subject(:mailer) { described_class.new(user).deliver } + + it "expect send email with correct data" do + expect(mailer).to be_a_instance_of(Mail::Message) + expect(mail_from(default_email_system)).to be_truthy + expect(sent_mail_to?(user.email, I18n.t("mail.user_context.registration.subject"))).to be_truthy + end + end + end +end diff --git a/spec/auction_fun_core_spec.rb b/spec/auction_fun_core_spec.rb index 9c14902..658fd7e 100644 --- a/spec/auction_fun_core_spec.rb +++ b/spec/auction_fun_core_spec.rb @@ -2,6 +2,6 @@ RSpec.describe AuctionFunCore do it "has a version number" do - expect(AuctionFunCore::VERSION).to eq("0.4.1") + expect(AuctionFunCore::VERSION).to eq("0.5.0") end end diff --git a/spec/support/mail.rb b/spec/support/mail.rb new file mode 100644 index 0000000..4eb79a0 --- /dev/null +++ b/spec/support/mail.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +IdleMailer.config do |config| + config.delivery_method = :test +end + +RSpec.configure do |config| + config.include IdleMailer::Testing::Helpers + + config.after :each do + IdleMailer::Testing.clear_mail! + end +end diff --git a/system/providers/core.rb b/system/providers/core.rb index 59f22c5..0092276 100644 --- a/system/providers/core.rb +++ b/system/providers/core.rb @@ -23,6 +23,7 @@ target.start(:events) target.start(:logger) target.start(:persistence) + target.start(:mail) end stop do diff --git a/system/providers/mail.rb b/system/providers/mail.rb new file mode 100644 index 0000000..a4d4d84 --- /dev/null +++ b/system/providers/mail.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# This file uses the rom gem to define a database configuration container +# and registers it with our application under the container key. +AuctionFunCore::Application.register_provider(:mail) do + prepare do + require "idlemailer" + end + + start do + IdleMailer.config do |config| + config.templates = Pathname.new(AuctionFunCore::Application.root).join("lib", "auction_fun_core", "services", "mail", "templates") + config.cache_templates = true + config.layout = "layout" + config.delivery_method = :smtp + config.delivery_options = { + address: target[:settings].smtp_address, + port: target[:settings].smtp_port + } + config.default_from = target[:settings].default_email_system + config.logger = nil + config.log_body = false + end + end +end diff --git a/system/providers/settings.rb b/system/providers/settings.rb index c80a3db..047e4f5 100644 --- a/system/providers/settings.rb +++ b/system/providers/settings.rb @@ -15,5 +15,11 @@ setting :logger_level, default: :info, constructor: Dry::Types["symbol"] .constructor { |value| value.to_s.downcase.to_sym } .enum(:trace, :unknown, :error, :fatal, :warn, :info, :debug) + setting :default_email_system, default: ENV.fetch("DEFAULT_EMAIL_SYSTEM"), + constructor: Dry::Types["string"].constrained(filled: true) + setting :smtp_address, default: ENV.fetch("SMTP_ADDRESS"), + constructor: Dry::Types["string"].constrained(filled: true) + setting :smtp_port, default: ENV.fetch("SMTP_PORT"), + constructor: Dry::Types["string"].constrained(filled: true) end end