auction_fun_core 0.8.5

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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +12 -0
  3. data/.env.development.template +7 -0
  4. data/.env.test.template +7 -0
  5. data/.rspec +3 -0
  6. data/.standard.yml +3 -0
  7. data/.tool-versions +5 -0
  8. data/CHANGELOG.md +145 -0
  9. data/CODE_OF_CONDUCT.md +84 -0
  10. data/LICENSE.txt +21 -0
  11. data/Procfile +4 -0
  12. data/README.md +141 -0
  13. data/Rakefile +13 -0
  14. data/auction_fun_core.gemspec +57 -0
  15. data/config/app.rb +5 -0
  16. data/config/application.rb +37 -0
  17. data/config/boot.rb +15 -0
  18. data/db/migrate/20240216200926_enable_postgres_extensions.rb +13 -0
  19. data/db/migrate/20240217115734_create_users.rb +28 -0
  20. data/db/migrate/20240220140151_create_custom_types.rb +11 -0
  21. data/db/migrate/20240220140254_create_staffs.rb +20 -0
  22. data/db/migrate/20240229142933_create_custom_types.rb +13 -0
  23. data/db/migrate/20240229143000_create_auctions.rb +29 -0
  24. data/db/migrate/20240304144422_create_bids.rb +19 -0
  25. data/db/seeds.rb +116 -0
  26. data/i18n/en-US/contracts/contracts.en-US.yml +72 -0
  27. data/i18n/en-US/mail/user_context/registration.en-US.yml +11 -0
  28. data/i18n/pt-BR/contracts/contracts.pt-BR.yml +72 -0
  29. data/i18n/pt-BR/mail/user_context/registration.pt-BR.yml +12 -0
  30. data/lib/auction_fun_core/business/configuration.rb +18 -0
  31. data/lib/auction_fun_core/business/token_generator.rb +17 -0
  32. data/lib/auction_fun_core/commands/auction_context/create_auction.rb +19 -0
  33. data/lib/auction_fun_core/commands/auction_context/delete_auction.rb +15 -0
  34. data/lib/auction_fun_core/commands/auction_context/update_auction.rb +18 -0
  35. data/lib/auction_fun_core/commands/bid_context/create_bid.rb +19 -0
  36. data/lib/auction_fun_core/commands/bid_context/delete_bid.rb +15 -0
  37. data/lib/auction_fun_core/commands/bid_context/update_bid.rb +18 -0
  38. data/lib/auction_fun_core/commands/staff_context/create_staff.rb +19 -0
  39. data/lib/auction_fun_core/commands/user_context/create_user.rb +19 -0
  40. data/lib/auction_fun_core/contracts/application_contract.rb +55 -0
  41. data/lib/auction_fun_core/contracts/auction_context/create_contract.rb +101 -0
  42. data/lib/auction_fun_core/contracts/auction_context/processor/finish_contract.rb +27 -0
  43. data/lib/auction_fun_core/contracts/auction_context/processor/pause_contract.rb +34 -0
  44. data/lib/auction_fun_core/contracts/auction_context/processor/start_contract.rb +46 -0
  45. data/lib/auction_fun_core/contracts/auction_context/processor/unpause_contract.rb +34 -0
  46. data/lib/auction_fun_core/contracts/bid_context/create_bid_closed_contract.rb +79 -0
  47. data/lib/auction_fun_core/contracts/bid_context/create_bid_penny_contract.rb +69 -0
  48. data/lib/auction_fun_core/contracts/bid_context/create_bid_standard_contract.rb +68 -0
  49. data/lib/auction_fun_core/contracts/staff_context/authentication_contract.rb +47 -0
  50. data/lib/auction_fun_core/contracts/staff_context/registration_contract.rb +52 -0
  51. data/lib/auction_fun_core/contracts/user_context/authentication_contract.rb +47 -0
  52. data/lib/auction_fun_core/contracts/user_context/email_confirmation_contract.rb +40 -0
  53. data/lib/auction_fun_core/contracts/user_context/phone_confirmation_contract.rb +40 -0
  54. data/lib/auction_fun_core/contracts/user_context/registration_contract.rb +63 -0
  55. data/lib/auction_fun_core/entities/auction.rb +17 -0
  56. data/lib/auction_fun_core/entities/bid.rb +10 -0
  57. data/lib/auction_fun_core/entities/staff.rb +21 -0
  58. data/lib/auction_fun_core/entities/user.rb +37 -0
  59. data/lib/auction_fun_core/events/app.rb +27 -0
  60. data/lib/auction_fun_core/events/listener.rb +89 -0
  61. data/lib/auction_fun_core/operations/auction_context/create_operation.rb +77 -0
  62. data/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb +61 -0
  63. data/lib/auction_fun_core/operations/auction_context/processor/pause_operation.rb +57 -0
  64. data/lib/auction_fun_core/operations/auction_context/processor/start_operation.rb +84 -0
  65. data/lib/auction_fun_core/operations/auction_context/processor/unpause_operation.rb +57 -0
  66. data/lib/auction_fun_core/operations/base.rb +11 -0
  67. data/lib/auction_fun_core/operations/bid_context/create_bid_closed_operation.rb +64 -0
  68. data/lib/auction_fun_core/operations/bid_context/create_bid_penny_operation.rb +64 -0
  69. data/lib/auction_fun_core/operations/bid_context/create_bid_standard_operation.rb +74 -0
  70. data/lib/auction_fun_core/operations/staff_context/authentication_operation.rb +52 -0
  71. data/lib/auction_fun_core/operations/staff_context/registration_operation.rb +76 -0
  72. data/lib/auction_fun_core/operations/user_context/authentication_operation.rb +52 -0
  73. data/lib/auction_fun_core/operations/user_context/email_confirmation_operation.rb +67 -0
  74. data/lib/auction_fun_core/operations/user_context/phone_confirmation_operation.rb +67 -0
  75. data/lib/auction_fun_core/operations/user_context/registration_operation.rb +105 -0
  76. data/lib/auction_fun_core/relations/auctions.rb +119 -0
  77. data/lib/auction_fun_core/relations/bids.rb +28 -0
  78. data/lib/auction_fun_core/relations/staffs.rb +27 -0
  79. data/lib/auction_fun_core/relations/users.rb +26 -0
  80. data/lib/auction_fun_core/repos/auction_context/auction_repository.rb +42 -0
  81. data/lib/auction_fun_core/repos/bid_context/bid_repository.rb +27 -0
  82. data/lib/auction_fun_core/repos/staff_context/staff_repository.rb +64 -0
  83. data/lib/auction_fun_core/repos/user_context/user_repository.rb +78 -0
  84. data/lib/auction_fun_core/services/mail/templates/layout.html.erb +72 -0
  85. data/lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb +170 -0
  86. data/lib/auction_fun_core/services/mail/user_context/registration_mailer.rb +25 -0
  87. data/lib/auction_fun_core/version.rb +8 -0
  88. data/lib/auction_fun_core/workers/application_job.rb +22 -0
  89. data/lib/auction_fun_core/workers/operations/auction_context/processor/finish_operation_job.rb +32 -0
  90. data/lib/auction_fun_core/workers/operations/auction_context/processor/start_operation_job.rb +32 -0
  91. data/lib/auction_fun_core/workers/services/mail/user_context/registration_mailer_job.rb +40 -0
  92. data/lib/auction_fun_core.rb +21 -0
  93. data/lib/tasks/database.rake +87 -0
  94. data/system/providers/background_job.rb +15 -0
  95. data/system/providers/core.rb +35 -0
  96. data/system/providers/db.rb +26 -0
  97. data/system/providers/events.rb +13 -0
  98. data/system/providers/logger.rb +11 -0
  99. data/system/providers/mail.rb +25 -0
  100. data/system/providers/persistence.rb +18 -0
  101. data/system/providers/settings.rb +26 -0
  102. metadata +400 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Services
5
+ module Mail
6
+ module UserContext
7
+ class RegistrationMailer
8
+ include IdleMailer::Mailer
9
+ include IdleMailer::TemplateManager
10
+
11
+ # @param user [ROM::Struct::User] The user object
12
+ def initialize(user)
13
+ @user = user
14
+ mail.to = user.email
15
+ mail.subject = I18n.t("mail.user_context.registration.subject")
16
+ end
17
+
18
+ def self.template_name
19
+ IdleMailer.config.templates.join("user_context/registration")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ VERSION = "0.8.5"
5
+
6
+ # Required class module is a gem dependency
7
+ class Version; end
8
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Workers
5
+ # Abstract base class for background jobs.
6
+ # @abstract
7
+ class ApplicationJob
8
+ MAX_RETRIES = 15
9
+ include Sidekiq::Job
10
+
11
+ def capture_exception(exception, attributes = {})
12
+ Application[:logger].error("#{exception.message}. Parameters: #{attributes}")
13
+ end
14
+
15
+ def backoff_exponential_job(retry_count, measure = "seconds")
16
+ max_retries = Float(2**retry_count)
17
+
18
+ rand(0..max_retries).send(measure)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Workers
5
+ module Operations
6
+ module AuctionContext
7
+ module Processor
8
+ ##
9
+ # BackgroundJob class for call finish auction operation.
10
+ #
11
+ class FinishOperationJob < Workers::ApplicationJob
12
+ include Import["repos.auction_context.auction_repository"]
13
+ include Import["operations.auction_context.processor.finish_operation"]
14
+
15
+ # @todo Add detailed documentation
16
+ def perform(auction_id, retry_count = 0)
17
+ auction = auction_repository.by_id!(auction_id)
18
+
19
+ finish_operation.call(auction.id)
20
+ rescue => e
21
+ capture_exception(e, {auction_id: auction_id, retry_count: retry_count})
22
+ raise if retry_count >= MAX_RETRIES
23
+
24
+ interval = backoff_exponential_job(retry_count)
25
+ self.class.perform_at(interval, auction_id, retry_count + 1)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Workers
5
+ module Operations
6
+ module AuctionContext
7
+ module Processor
8
+ ##
9
+ # BackgroundJob class for call start auction operation.
10
+ #
11
+ class StartOperationJob < AuctionFunCore::Workers::ApplicationJob
12
+ include Import["repos.auction_context.auction_repository"]
13
+ include Import["operations.auction_context.processor.start_operation"]
14
+
15
+ # @todo Add detailed documentation
16
+ def perform(auction_id, retry_count = 0)
17
+ auction = auction_repository.by_id!(auction_id)
18
+
19
+ start_operation.call(auction_id: auction.id, kind: auction.kind, stopwatch: auction.stopwatch)
20
+ rescue => e
21
+ capture_exception(e, {auction_id: auction_id, retry_count: retry_count})
22
+ raise if retry_count >= MAX_RETRIES
23
+
24
+ interval = backoff_exponential_job(retry_count)
25
+ self.class.perform_at(interval, auction_id, retry_count + 1)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Workers
5
+ module Services
6
+ module Mail
7
+ module UserContext
8
+ ##
9
+ # Background job class responsible for adding emails to the queue.
10
+ #
11
+ class RegistrationMailerJob < AuctionFunCore::Workers::ApplicationJob
12
+ include Import["repos.user_context.user_repository"]
13
+
14
+ # @param user_id [Integer] user ID
15
+ def perform(user_id, retry_count = 0)
16
+ user = user_repository.by_id!(user_id)
17
+
18
+ registration_mailer.new(user).deliver
19
+ rescue => e
20
+ capture_exception(e, {user_id: user_id, retry_count: retry_count})
21
+ raise e if retry_count >= MAX_RETRIES
22
+
23
+ interval = backoff_exponential_job(retry_count)
24
+ self.class.perform_at(interval, user_id, retry_count + 1)
25
+ end
26
+
27
+ private
28
+
29
+ # Since the shipping code structure does not follow project conventions,
30
+ # making the default injection dependency would be more complicated.
31
+ # Therefore, here I directly explain the class to be called.
32
+ def registration_mailer
33
+ AuctionFunCore::Services::Mail::UserContext::RegistrationMailer
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "auction_fun_core/version"
4
+ require "zeitwerk"
5
+
6
+ require_relative "../config/boot"
7
+ require_relative "../config/application"
8
+
9
+ module AuctionFunCore
10
+ class Error < StandardError; end
11
+
12
+ def self.root
13
+ File.expand_path "..", __dir__
14
+ end
15
+
16
+ def self.lib_path
17
+ File.expand_path ".", __dir__
18
+ end
19
+
20
+ autoload :Application, Pathname.new(File.expand_path("../config/application", __dir__))
21
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom-sql"
4
+ require "rom/sql/rake_task"
5
+ require "pry"
6
+ require "uri"
7
+
8
+ namespace :auction_fun_core do
9
+ namespace :db do
10
+ uri = URI.parse(ENV.fetch("DATABASE_URL"))
11
+ @database_name = (uri.path || "").split("/").last
12
+ @database_host = uri.host
13
+
14
+ desc "Perform migration reset (full erase and migration up)"
15
+ task :rom_configuration do
16
+ Rake::Task["auction_fun_core:db:setup"].invoke
17
+ end
18
+
19
+ desc "Prepare database for running migrations"
20
+ task :setup do
21
+ AuctionFunCore::Application.start(:db)
22
+ ROM::SQL::RakeSupport.env = ROM.container(AuctionFunCore::Application[:db_config])
23
+ end
24
+
25
+ desc "Perform migration reset (full erase and migration up)"
26
+ task reset: :rom_configuration do
27
+ ROM::SQL::RakeSupport.run_migrations(target: 0)
28
+ ROM::SQL::RakeSupport.run_migrations
29
+ puts "<= db:reset executed"
30
+ end
31
+
32
+ desc "Create a postgres database"
33
+ task :create_database, [:userdb] do |_t, args|
34
+ command = "CREATE DATABASE #{@database_name} LOCALE 'en_US.utf8' ENCODING UTF8 TEMPLATE template0;"
35
+
36
+ sh %(psql -h #{@database_host} -U #{args[:userdb]} -c "#{command}")
37
+ end
38
+
39
+ desc "Drop a postgres database"
40
+ task :drop_database, [:userdb] do |_t, args|
41
+ sh %(psql -h #{@database_host} -U #{args[:userdb]} -c "DROP DATABASE #{@database_name};")
42
+ end
43
+
44
+ desc "Create a migration (parameters: NAME, VERSION)"
45
+ task :create_migration, %i[name version] => :rom_configuration do |_, args|
46
+ name, version = args.values_at(:name, :version)
47
+
48
+ if name.nil?
49
+ puts "No NAME specified. Example usage:
50
+ `rake db:create_migration[create_users]`"
51
+ exit
52
+ end
53
+
54
+ path = ROM::SQL::RakeSupport.create_migration(*[name, version].compact)
55
+
56
+ puts "<= migration file created #{path}"
57
+ end
58
+
59
+ desc "Migrate the database (options [version_number])]"
60
+ task :migrate, [:version] => :rom_configuration do |_, args|
61
+ version = args[:version]
62
+
63
+ if version.nil?
64
+ ROM::SQL::RakeSupport.run_migrations
65
+ puts "<= db:migrate executed"
66
+ else
67
+ ROM::SQL::RakeSupport.run_migrations(target: version.to_i)
68
+ puts "<= db:migrate version=[#{version}] executed"
69
+ end
70
+ end
71
+
72
+ desc "Perform migration down (removes all tables)"
73
+ task clean: :rom_configuration do
74
+ ROM::SQL::RakeSupport.run_migrations(target: 0)
75
+ puts "<= db:clean executed"
76
+ end
77
+
78
+ desc "Seed data"
79
+ task :seed_database do
80
+ Rake::Task["auction_fun_core:db:setup"].invoke
81
+ seed_file = AuctionFunCore::Application.root.join("db", "seeds.rb")
82
+ raise "You tried to load seed data, but no seed loader is specified" unless seed_file
83
+
84
+ load(seed_file)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ AuctionFunCore::Application.register_provider(:background_job) do
4
+ prepare do
5
+ require "sidekiq"
6
+ end
7
+
8
+ start do
9
+ Sidekiq.configure_server do |config|
10
+ config.logger = target[:settings].logger
11
+ config.average_scheduled_poll_interval = 3
12
+ config.redis = {url: target[:settings].redis_url}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file load requirements to main API.
4
+ AuctionFunCore::Application.register_provider(:core) do
5
+ prepare do
6
+ require "dry/validation"
7
+ require "dry/monads/all"
8
+ require "dry/matcher/result_matcher"
9
+ require "active_support"
10
+ require "active_support/core_ext/object/blank"
11
+ require "active_support/time"
12
+ require "dry/events"
13
+
14
+ Dry::Schema.load_extensions(:hints)
15
+ Dry::Schema.load_extensions(:info)
16
+ Dry::Schema.load_extensions(:monads)
17
+ Dry::Validation.load_extensions(:monads)
18
+ Dry::Types.load_extensions(:monads)
19
+ end
20
+
21
+ start do
22
+ target.start(:settings)
23
+ target.start(:events)
24
+ target.start(:logger)
25
+ target.start(:persistence)
26
+ target.start(:mail)
27
+ end
28
+
29
+ stop do
30
+ target.stop(:settings)
31
+ target.stop(:events)
32
+ target.stop(:logger)
33
+ target.stop(:persistence)
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file uses the rom gem to define a database configuration container
4
+ # and registers it with our application under the container key.
5
+ AuctionFunCore::Application.register_provider(:db) do
6
+ prepare do
7
+ require "rom"
8
+ require "rom-sql"
9
+ end
10
+
11
+ start do
12
+ connection = Sequel.connect(
13
+ AuctionFunCore::Application[:settings].database_url,
14
+ extensions: %i[pg_array pg_json pg_enum]
15
+ )
16
+ migrator = ROM::SQL::Migration::Migrator.new(
17
+ connection, path: Pathname.new(AuctionFunCore::Application.root.join("db/migrate"))
18
+ )
19
+ register(:db_connection, connection)
20
+ register(:db_config, ROM::Configuration.new(:sql, connection, migrator: migrator))
21
+ end
22
+
23
+ stop do
24
+ container["db_connection"].disconnect
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ AuctionFunCore::Application.register_provider(:events) do
4
+ start do
5
+ register(:event, AuctionFunCore::Events::App.new)
6
+ register(:listener, AuctionFunCore::Events::Listener.new)
7
+ register(:subscription, event.subscribe(listener))
8
+ end
9
+
10
+ stop do
11
+ container["subscription"].unsubscribe(listener)
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ AuctionFunCore::Application.register_provider(:logger) do
4
+ prepare do
5
+ require "logger"
6
+ end
7
+
8
+ start do
9
+ register(:logger, AuctionFunCore::Application[:settings].logger)
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file uses the rom gem to define a database configuration container
4
+ # and registers it with our application under the container key.
5
+ AuctionFunCore::Application.register_provider(:mail) do
6
+ prepare do
7
+ require "idlemailer"
8
+ end
9
+
10
+ start do
11
+ IdleMailer.config do |config|
12
+ config.templates = Pathname.new(AuctionFunCore::Application.root).join("lib", "auction_fun_core", "services", "mail", "templates")
13
+ config.cache_templates = true
14
+ config.layout = "layout"
15
+ config.delivery_method = :smtp
16
+ config.delivery_options = {
17
+ address: target[:settings].smtp_address,
18
+ port: target[:settings].smtp_port
19
+ }
20
+ config.default_from = target[:settings].default_email_system
21
+ config.logger = nil
22
+ config.log_body = false
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file uses the rom gem to define a database persistence container
4
+ # and registers it with our application under the container key.
5
+ AuctionFunCore::Application.register_provider(:persistence) do
6
+ prepare do
7
+ require "rom"
8
+ require "rom-sql"
9
+ end
10
+
11
+ start do
12
+ configuration ||= ROM::Configuration.new(
13
+ :sql, AuctionFunCore::Application[:settings].database_url, extensions: %i[pg_array pg_json pg_enum]
14
+ )
15
+ configuration.auto_registration("#{target.root}/lib/auction_fun_core")
16
+ register(:container, ROM.container(configuration))
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/provider_sources"
4
+
5
+ # This file uses the rom gem to define a database configuration container
6
+ # and registers it with our application under the container key.
7
+ AuctionFunCore::Application.register_provider(:settings, from: :dry_system) do
8
+ settings do
9
+ setting :logger, default: Logger.new($stdout)
10
+ setting :database_url, default: ENV.fetch("DATABASE_URL"),
11
+ constructor: Dry::Types["string"].constrained(filled: true)
12
+ setting :redis_url, default: ENV.fetch("REDIS_URL"),
13
+ constructor: Dry::Types["string"].constrained(filled: true)
14
+ setting :logger_level, default: :info, constructor: Dry::Types["symbol"]
15
+ .constructor { |value| value.to_s.downcase.to_sym }
16
+ .enum(:trace, :unknown, :error, :fatal, :warn, :info, :debug)
17
+ setting :default_email_system, default: ENV.fetch("DEFAULT_EMAIL_SYSTEM"),
18
+ constructor: Dry::Types["string"].constrained(filled: true)
19
+ setting :smtp_address, default: ENV.fetch("SMTP_ADDRESS"),
20
+ constructor: Dry::Types["string"].constrained(filled: true)
21
+ setting :smtp_port, default: ENV.fetch("SMTP_PORT"),
22
+ constructor: Dry::Types["string"].constrained(filled: true)
23
+ setting :default_currency, constructor: Dry::Types["string"].constrained(filled: true),
24
+ default: Money.default_currency
25
+ end
26
+ end