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,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module AuctionContext
6
+ # Contract class to create new auctions.
7
+ class CreateContract < Contracts::ApplicationContract
8
+ include AuctionFunCore::Business::Configuration
9
+
10
+ REQUIRED_FINISHED_AT = AUCTION_KINDS - ["penny"]
11
+
12
+ option :staff_repo, default: proc { Repos::StaffContext::StaffRepository.new }
13
+
14
+ # @param [Hash] opts Sets an allowed list of parameters, as well as some initial validations.
15
+ params do
16
+ required(:staff_id).filled(:integer)
17
+ required(:title).filled(:string, size?: (AUCTION_MIN_TITLE_LENGTH..AUCTION_MAX_TITLE_LENGTH))
18
+ required(:kind).value(included_in?: AUCTION_KINDS)
19
+ required(:started_at).filled(:time)
20
+ optional(:description)
21
+ optional(:finished_at).filled(:time)
22
+ optional(:initial_bid_cents).filled(:integer)
23
+ optional(:stopwatch).filled(:integer)
24
+
25
+ # Keys with a blank value are discarded.
26
+ before(:value_coercer) do |result|
27
+ result.to_h.compact
28
+ end
29
+
30
+ # By default, the minimum bid cents is initially equal to initial_bid_cents.
31
+ after(:value_coercer) do |result|
32
+ result.update(minimal_bid_cents: result[:initial_bid_cents]) if result[:initial_bid_cents]
33
+ end
34
+ end
35
+
36
+ # Validation for staff.
37
+ # Validate whether the given staff is valid at the database level.
38
+ rule(:staff_id) do |context:|
39
+ context[:staff] ||= staff_repo.by_id(value)
40
+ key.failure(I18n.t("contracts.errors.custom.default.not_found")) unless context[:staff]
41
+ end
42
+
43
+ # Validation for started_at.
44
+ # Validates if the entered date is greater than or equal to the current time.
45
+ rule(:started_at) do
46
+ key.failure(I18n.t("contracts.errors.custom.default.future")) if key? && value <= Time.current
47
+ end
48
+
49
+ # Specific validation when the auction type is informed and it is not penny,
50
+ # it must be mandatory to inform the auction closing date/time
51
+ rule(:finished_at, :kind) do
52
+ if key?(:kind) && !key?(:finished_at) && REQUIRED_FINISHED_AT.include?(values[:kind])
53
+ key.failure(I18n.t("contracts.errors.filled?"))
54
+ end
55
+ end
56
+
57
+ # Basic specific validation to check if the auction end time
58
+ # is less than or equal to the start time.
59
+ rule(:finished_at, :started_at) do
60
+ if key?(:finished_at) && (values[:finished_at] <= values[:started_at])
61
+ key.failure(I18n.t("contracts.errors.custom.auction_context.create.finished_at"))
62
+ end
63
+ end
64
+
65
+ # Validation for initial bid amount.
66
+ #
67
+ rule(:initial_bid_cents) do
68
+ # Must be filled if auction kind is not type penny.
69
+ key.failure(I18n.t("contracts.errors.filled?")) if !key? && REQUIRED_FINISHED_AT.include?(values[:kind])
70
+
71
+ # Must be greater than zero if action kind is not type penny.
72
+ if key? && REQUIRED_FINISHED_AT.include?(values[:kind]) && values[:initial_bid_cents] <= 0
73
+ key.failure(I18n.t("contracts.errors.gt?", num: 0))
74
+ end
75
+
76
+ # Must be equal to zero if auction kind is type penny.
77
+ if key? && values[:kind] == "penny" && !values[:initial_bid_cents].zero?
78
+ key.failure(I18n.t("contracts.errors.eql?", left: 0))
79
+ end
80
+ end
81
+
82
+ # Validation for stopwatch.
83
+ #
84
+ rule(:stopwatch) do
85
+ # Must be filled if auction kind is type penny.
86
+ key.failure(I18n.t("contracts.errors.filled?")) if !key? && values[:kind] == "penny"
87
+
88
+ # Must be an integer between 15 and 60.
89
+ if key? && values[:kind] == "penny" && !value.between?(AUCTION_STOPWATCH_MIN_VALUE, AUCTION_STOPWATCH_MAX_VALUE)
90
+ key.failure(
91
+ I18n.t(
92
+ "contracts.errors.included_in?.arg.range",
93
+ list_left: AUCTION_STOPWATCH_MIN_VALUE, list_right: AUCTION_STOPWATCH_MAX_VALUE
94
+ )
95
+ )
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module AuctionContext
6
+ module Processor
7
+ ##
8
+ # Contract class for finish auctions.
9
+ #
10
+ class FinishContract < Contracts::ApplicationContract
11
+ option :auction_repo, default: proc { Repos::AuctionContext::AuctionRepository.new }
12
+
13
+ params do
14
+ required(:auction_id).filled(:integer)
15
+ end
16
+
17
+ # Validation for auction.
18
+ # Validates if the auction exists in the database.
19
+ rule(:auction_id) do |context:|
20
+ context[:auction] ||= auction_repo.by_id(value)
21
+ key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:auction]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module AuctionContext
6
+ module Processor
7
+ ##
8
+ # Contract class for pause auction.
9
+ #
10
+ class PauseContract < Contracts::ApplicationContract
11
+ option :auction_repository, default: proc { Repos::AuctionContext::AuctionRepository.new }
12
+
13
+ params do
14
+ required(:auction_id).filled(:integer)
15
+ end
16
+
17
+ # Validation for auction.
18
+ # Validates if the auction exists in the database and check if only auctions
19
+ # with a "running" status can be paused.
20
+ rule(:auction_id) do |context:|
21
+ context[:auction] ||= auction_repository.by_id(value)
22
+ key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:auction]
23
+
24
+ unless %w[running].include?(context[:auction].status)
25
+ key.failure(
26
+ I18n.t("contracts.errors.custom.bids.invalid_status", status: context[:auction].status)
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module AuctionContext
6
+ module Processor
7
+ ##
8
+ # Contract class for start auctions.
9
+ #
10
+ class StartContract < Contracts::ApplicationContract
11
+ include AuctionFunCore::Business::Configuration
12
+
13
+ option :auction_repo, default: proc { Repos::AuctionContext::AuctionRepository.new }
14
+
15
+ params do
16
+ required(:auction_id).filled(:integer)
17
+ required(:kind).value(included_in?: AUCTION_KINDS)
18
+ optional(:stopwatch).filled(:integer)
19
+ end
20
+
21
+ # Validation for auction.
22
+ # Validates if the auction exists in the database.
23
+ rule(:auction_id) do |context:|
24
+ context[:auction] ||= auction_repo.by_id(value)
25
+ key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:auction]
26
+ end
27
+
28
+ # Validation for stopwatch.
29
+ #
30
+ rule(:stopwatch) do
31
+ # Must be filled if auction kind is type penny.
32
+ key.failure(I18n.t("contracts.errors.filled?")) if !key? && values[:kind] == "penny"
33
+
34
+ # Must be an integer between 15 and 60.
35
+ if key? && values[:kind] == "penny" && !value.between?(AUCTION_STOPWATCH_MIN_VALUE, AUCTION_STOPWATCH_MAX_VALUE)
36
+ key.failure(
37
+ I18n.t("contracts.errors.included_in?.arg.range",
38
+ list_left: AUCTION_STOPWATCH_MIN_VALUE, list_right: AUCTION_STOPWATCH_MAX_VALUE)
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module AuctionContext
6
+ module Processor
7
+ ##
8
+ # Contract class for unpause auction.
9
+ #
10
+ class UnpauseContract < Contracts::ApplicationContract
11
+ option :auction_repository, default: proc { Repos::AuctionContext::AuctionRepository.new }
12
+
13
+ params do
14
+ required(:auction_id).filled(:integer)
15
+ end
16
+
17
+ # Validation for auction.
18
+ # Validates if the auction exists in the database and and checks if the
19
+ # auction has a paused status.
20
+ rule(:auction_id) do |context:|
21
+ context[:auction] ||= auction_repository.by_id(value)
22
+ key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:auction]
23
+
24
+ unless %w[paused].include?(context[:auction].status)
25
+ key.failure(
26
+ I18n.t("contracts.errors.custom.bids.invalid_status", status: context[:auction].status)
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module BidContext
6
+ # Contract class to create new bids.
7
+ class CreateBidClosedContract < Contracts::ApplicationContract
8
+ option :user_repository, default: proc { Repos::UserContext::UserRepository.new }
9
+ option :auction_repository, default: proc { Repos::AuctionContext::AuctionRepository.new }
10
+ option :bid_repository, default: proc { Repos::BidContext::BidRepository.new }
11
+
12
+ # @param [Hash] opts Sets an allowed list of parameters, as well as some initial validations.
13
+ params do
14
+ required(:auction_id).filled(:integer)
15
+ required(:user_id).filled(:integer)
16
+ required(:value_cents).filled(:integer)
17
+
18
+ # Keys with a blank value are discarded.
19
+ before(:value_coercer) do |result|
20
+ result.to_h.compact
21
+ end
22
+ end
23
+
24
+ # Validation for auction.
25
+ # validate whether the given auction is valid at the database level.
26
+ # validate if the auction is open to receive bids
27
+ rule(:auction_id) do |context:|
28
+ context[:auction] ||= auction_repository.by_id(value)
29
+
30
+ if context[:auction]
31
+ if context[:auction].kind != "closed"
32
+ key.failure(I18n.t("contracts.errors.custom.bids.invalid_kind", kind: "closed"))
33
+ end
34
+
35
+ unless %w[scheduled running].include?(context[:auction].status)
36
+ key.failure(
37
+ I18n.t("contracts.errors.custom.bids.invalid_status", status: context[:auction].status)
38
+ )
39
+ end
40
+ else
41
+ key.failure(I18n.t("contracts.errors.custom.not_found"))
42
+ end
43
+ end
44
+
45
+ # Validation for user.
46
+ # Validate whether the given user is valid at the database level.
47
+ # Validates if user has already placed a bid
48
+ rule(:user_id) do |context:|
49
+ context[:user] ||= user_repository.by_id(value)
50
+
51
+ if context[:user]
52
+ if bid_repository.exists?(auction_id: values[:auction_id], user_id: value)
53
+ key.failure(I18n.t("contracts.errors.custom.bids.already_bidded"))
54
+ end
55
+ else
56
+ key.failure(I18n.t("contracts.errors.custom.not_found"))
57
+ end
58
+ end
59
+
60
+ # Validation for value bid.
61
+ # The bid amount must be greater than or equal to the starting bid.
62
+ rule(:value_cents) do |context:|
63
+ unless rule_error?(:user_id)
64
+ closed_auction_bid_value_is_gteq_initial_bid?(key, value, context[:auction].initial_bid_cents)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # Checks if bid amount must be greater than or equal to the starting bid.
71
+ def closed_auction_bid_value_is_gteq_initial_bid?(key, value_cents, minimal_bid_cents)
72
+ return unless value_cents < minimal_bid_cents
73
+
74
+ key.failure(I18n.t("contracts.errors.gteq?", num: Money.new(minimal_bid_cents).to_f))
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module BidContext
6
+ # Contract class to create new bids.
7
+ class CreateBidPennyContract < Contracts::ApplicationContract
8
+ option :user_repo, default: proc { Repos::UserContext::UserRepository.new }
9
+ option :auction_repo, default: proc { Repos::AuctionContext::AuctionRepository.new }
10
+
11
+ # @param [Hash] opts Sets an allowed list of parameters, as well as some initial validations.
12
+ params do
13
+ required(:auction_id).filled(:integer)
14
+ required(:user_id).filled(:integer)
15
+
16
+ # Keys with a blank value are discarded.
17
+ before(:value_coercer) do |result|
18
+ result.to_h.compact
19
+ end
20
+ end
21
+
22
+ # Validation for auction.
23
+ # validate whether the given auction is valid at the database level.
24
+ # validate if the auction is open to receive bids
25
+ rule(:auction_id) do |context:|
26
+ context[:auction] ||= auction_repo.by_id(value)
27
+
28
+ if context[:auction]
29
+ if context[:auction].kind != "penny"
30
+ key.failure(I18n.t("contracts.errors.custom.bids.invalid_kind", kind: "penny"))
31
+ end
32
+
33
+ unless %w[scheduled running].include?(context[:auction].status)
34
+ key.failure(
35
+ I18n.t("contracts.errors.custom.bids.invalid_status", status: context[:auction].status)
36
+ )
37
+ end
38
+ else
39
+ key.failure(I18n.t("contracts.errors.custom.not_found"))
40
+ end
41
+ end
42
+
43
+ # Validation for user.
44
+ # Validate whether the given user is valid at the database level.
45
+ # Validates if user has enough balance to bid.
46
+ rule(:user_id) do |context:|
47
+ context[:user] ||= user_repo.by_id(value)
48
+
49
+ if context[:user]
50
+ unless rule_error?(:auction_id)
51
+ penny_auction_check_user_has_balance?(
52
+ key, context[:auction].initial_bid_cents, context[:user].balance_cents
53
+ )
54
+ end
55
+ else
56
+ key.failure(I18n.t("contracts.errors.custom.not_found"))
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Checks if user has enough balance to bid.
63
+ def penny_auction_check_user_has_balance?(key, auction_bid_cents, balance_cents)
64
+ key.failure(I18n.t("contracts.errors.custom.bids.insufficient_balance")) if balance_cents < auction_bid_cents
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module BidContext
6
+ # Contract class to create new bids.
7
+ class CreateBidStandardContract < Contracts::ApplicationContract
8
+ option :user_repo, default: proc { Repos::UserContext::UserRepository.new }
9
+ option :auction_repo, default: proc { Repos::AuctionContext::AuctionRepository.new }
10
+
11
+ # @param [Hash] opts Sets an allowed list of parameters, as well as some initial validations.
12
+ params do
13
+ required(:auction_id).filled(:integer)
14
+ required(:user_id).filled(:integer)
15
+ required(:value_cents).filled(:integer)
16
+
17
+ # Keys with a blank value are discarded.
18
+ before(:value_coercer) do |result|
19
+ result.to_h.compact
20
+ end
21
+ end
22
+
23
+ # Validation for auction.
24
+ # validate whether the given auction is valid at the database level.
25
+ # validate if the auction is open to receive bids
26
+ rule(:auction_id) do |context:|
27
+ context[:auction] ||= auction_repo.by_id(value)
28
+
29
+ if context[:auction]
30
+ if context[:auction].kind != "standard"
31
+ key.failure(I18n.t("contracts.errors.custom.bids.invalid_kind", kind: "standard"))
32
+ end
33
+
34
+ unless %w[scheduled running].include?(context[:auction].status)
35
+ key.failure(
36
+ I18n.t("contracts.errors.custom.bids.invalid_status", status: context[:auction].status)
37
+ )
38
+ end
39
+ else
40
+ key.failure(I18n.t("contracts.errors.custom.not_found"))
41
+ end
42
+ end
43
+
44
+ # Validation for user.
45
+ # Validate whether the given user is valid at the database level.
46
+ rule(:user_id) do |context:|
47
+ context[:user] ||= user_repo.by_id(value)
48
+ key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:user]
49
+ end
50
+
51
+ # Validation for value bid.
52
+ # must be greater than or equal to the auction's minimal bid.
53
+ rule(:value_cents) do |context:|
54
+ standard_auction_valid_bid?(key, value, context[:auction].minimal_bid_cents)
55
+ end
56
+
57
+ private
58
+
59
+ # Checks if the bid amount is greather than or equal to minimum bid.
60
+ def standard_auction_valid_bid?(key, value_cents, minimal_bid_cents)
61
+ return if value_cents >= minimal_bid_cents
62
+
63
+ key.failure(I18n.t("contracts.errors.gteq?", num: Money.new(minimal_bid_cents).to_f))
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module StaffContext
6
+ # Contract class to authenticate staff.
7
+ class AuthenticationContract < ApplicationContract
8
+ I18N_SCOPE = "contracts.errors.custom.default"
9
+
10
+ option :staff_repository, default: proc { Repos::StaffContext::StaffRepository.new }
11
+
12
+ params do
13
+ required(:login)
14
+ required(:password)
15
+
16
+ before(:value_coercer) do |result|
17
+ result.to_h.compact
18
+ end
19
+ end
20
+
21
+ rule(:login).validate(:login_format)
22
+ rule(:password).validate(:password_format)
23
+
24
+ # Validation for login.
25
+ # Searches for the staff in the database from the login, and, if found,
26
+ # compares the entered password.
27
+ rule do |context:|
28
+ next if (rule_error?(:login) || schema_error?(:login)) || (rule_error?(:password) || schema_error?(:password))
29
+
30
+ context[:staff] ||= staff_repository.by_login(values[:login])
31
+
32
+ next if context[:staff].present? && context[:staff].active? && (BCrypt::Password.new(context[:staff].password_digest) == values[:password])
33
+
34
+ if context[:staff].blank? || (BCrypt::Password.new(context[:staff].password_digest) != values[:password])
35
+ key(:base).failure(I18n.t("login_not_found", scope: I18N_SCOPE))
36
+ end
37
+
38
+ if context[:staff].present? && context[:staff].inactive?
39
+ key(:base).failure(I18n.t("inactive_account", scope: I18N_SCOPE))
40
+ end
41
+
42
+ key(:base).failure(I18n.t("login_not_found", scope: I18N_SCOPE))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module StaffContext
6
+ # Contract class to create new staff.
7
+ class RegistrationContract < ApplicationContract
8
+ I18N_SCOPE = "contracts.errors.custom.default"
9
+
10
+ option :staff_repository, default: proc { Repos::StaffContext::StaffRepository.new }
11
+
12
+ # @param [Hash] opts Sets an allowed list of parameters, as well as some initial validations.
13
+ params do
14
+ required(:name)
15
+ required(:email)
16
+ required(:phone)
17
+
18
+ before(:value_coercer) do |result|
19
+ result.to_h.compact
20
+ end
21
+
22
+ # Normalize and add default values
23
+ after(:value_coercer) do |result|
24
+ result.update(email: result[:email].strip.downcase) if result[:email]
25
+ result.update(phone: result[:phone].tr_s("^0-9", "")) if result[:phone]
26
+ end
27
+ end
28
+
29
+ rule(:name).validate(:name_format)
30
+
31
+ # Validation for email.
32
+ # It must validate the format and uniqueness in the database.
33
+ rule(:email).validate(:email_format)
34
+ rule(:email) do
35
+ # Email should be unique on database
36
+ if !rule_error?(:email) && staff_repository.exists?(email: value)
37
+ key.failure(I18n.t(:taken, scope: I18N_SCOPE))
38
+ end
39
+ end
40
+
41
+ # Validation for phone.
42
+ # It must validate the format and uniqueness in the database.
43
+ rule(:phone).validate(:phone_format)
44
+ rule(:phone) do
45
+ if !rule_error?(:phone) && staff_repository.exists?(phone: value)
46
+ key.failure(I18n.t(:taken, scope: I18N_SCOPE))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module UserContext
6
+ # Contract class to authenticate users.
7
+ class AuthenticationContract < ApplicationContract
8
+ I18N_SCOPE = "contracts.errors.custom.default"
9
+
10
+ option :user_repository, default: proc { Repos::UserContext::UserRepository.new }
11
+
12
+ params do
13
+ required(:login)
14
+ required(:password)
15
+
16
+ before(:value_coercer) do |result|
17
+ result.to_h.compact
18
+ end
19
+ end
20
+
21
+ rule(:login).validate(:login_format)
22
+ rule(:password).validate(:password_format)
23
+
24
+ # Validation for login.
25
+ # Searches for the user in the database from the login, and, if found,
26
+ # compares the entered password.
27
+ rule do |context:|
28
+ next if (rule_error?(:login) || schema_error?(:login)) || (rule_error?(:password) || schema_error?(:password))
29
+
30
+ context[:user] ||= user_repository.by_login(values[:login])
31
+
32
+ next if context[:user].present? && context[:user].active? && (BCrypt::Password.new(context[:user].password_digest) == values[:password])
33
+
34
+ if context[:user].blank? || (BCrypt::Password.new(context[:user].password_digest) != values[:password])
35
+ key(:base).failure(I18n.t("login_not_found", scope: I18N_SCOPE))
36
+ end
37
+
38
+ if context[:user].present? && context[:user].inactive?
39
+ key(:base).failure(I18n.t("inactive_account", scope: I18N_SCOPE))
40
+ end
41
+
42
+ key(:base).failure(I18n.t("login_not_found", scope: I18N_SCOPE))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AuctionFunCore
4
+ module Contracts
5
+ module UserContext
6
+ # Contract class responsible for validating the confirmation token for email.
7
+ class EmailConfirmationContract < ApplicationContract
8
+ I18N_SCOPE = "contracts.errors.custom.default"
9
+
10
+ option :user_repository, default: proc { Repos::UserContext::UserRepository.new }
11
+
12
+ params do
13
+ required(:email_confirmation_token)
14
+
15
+ before(:value_coercer) do |result|
16
+ result.to_h.compact
17
+ end
18
+ end
19
+
20
+ # Validation for email_confirmation_token.
21
+ # Searches for the user in the database from the email_confirmation_token
22
+ rule do |context:|
23
+ next if schema_error?(:email_confirmation_token)
24
+
25
+ context[:user] ||= user_repository.by_email_confirmation_token(values[:email_confirmation_token])
26
+
27
+ next if context[:user].present?
28
+
29
+ key(:email_confirmation_token).failure(I18n.t("not_found", scope: I18N_SCOPE))
30
+ end
31
+
32
+ rule do |context:|
33
+ next if context[:user].blank? || context[:user].active?
34
+
35
+ key(:base).failure(I18n.t("inactive_account", scope: I18N_SCOPE))
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end