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.
- checksums.yaml +7 -0
- data/.editorconfig +12 -0
- data/.env.development.template +7 -0
- data/.env.test.template +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/.tool-versions +5 -0
- data/CHANGELOG.md +145 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/Procfile +4 -0
- data/README.md +141 -0
- data/Rakefile +13 -0
- data/auction_fun_core.gemspec +57 -0
- data/config/app.rb +5 -0
- data/config/application.rb +37 -0
- data/config/boot.rb +15 -0
- data/db/migrate/20240216200926_enable_postgres_extensions.rb +13 -0
- data/db/migrate/20240217115734_create_users.rb +28 -0
- data/db/migrate/20240220140151_create_custom_types.rb +11 -0
- data/db/migrate/20240220140254_create_staffs.rb +20 -0
- data/db/migrate/20240229142933_create_custom_types.rb +13 -0
- data/db/migrate/20240229143000_create_auctions.rb +29 -0
- data/db/migrate/20240304144422_create_bids.rb +19 -0
- data/db/seeds.rb +116 -0
- data/i18n/en-US/contracts/contracts.en-US.yml +72 -0
- data/i18n/en-US/mail/user_context/registration.en-US.yml +11 -0
- data/i18n/pt-BR/contracts/contracts.pt-BR.yml +72 -0
- data/i18n/pt-BR/mail/user_context/registration.pt-BR.yml +12 -0
- data/lib/auction_fun_core/business/configuration.rb +18 -0
- data/lib/auction_fun_core/business/token_generator.rb +17 -0
- data/lib/auction_fun_core/commands/auction_context/create_auction.rb +19 -0
- data/lib/auction_fun_core/commands/auction_context/delete_auction.rb +15 -0
- data/lib/auction_fun_core/commands/auction_context/update_auction.rb +18 -0
- data/lib/auction_fun_core/commands/bid_context/create_bid.rb +19 -0
- data/lib/auction_fun_core/commands/bid_context/delete_bid.rb +15 -0
- data/lib/auction_fun_core/commands/bid_context/update_bid.rb +18 -0
- data/lib/auction_fun_core/commands/staff_context/create_staff.rb +19 -0
- data/lib/auction_fun_core/commands/user_context/create_user.rb +19 -0
- data/lib/auction_fun_core/contracts/application_contract.rb +55 -0
- data/lib/auction_fun_core/contracts/auction_context/create_contract.rb +101 -0
- data/lib/auction_fun_core/contracts/auction_context/processor/finish_contract.rb +27 -0
- data/lib/auction_fun_core/contracts/auction_context/processor/pause_contract.rb +34 -0
- data/lib/auction_fun_core/contracts/auction_context/processor/start_contract.rb +46 -0
- data/lib/auction_fun_core/contracts/auction_context/processor/unpause_contract.rb +34 -0
- data/lib/auction_fun_core/contracts/bid_context/create_bid_closed_contract.rb +79 -0
- data/lib/auction_fun_core/contracts/bid_context/create_bid_penny_contract.rb +69 -0
- data/lib/auction_fun_core/contracts/bid_context/create_bid_standard_contract.rb +68 -0
- data/lib/auction_fun_core/contracts/staff_context/authentication_contract.rb +47 -0
- data/lib/auction_fun_core/contracts/staff_context/registration_contract.rb +52 -0
- data/lib/auction_fun_core/contracts/user_context/authentication_contract.rb +47 -0
- data/lib/auction_fun_core/contracts/user_context/email_confirmation_contract.rb +40 -0
- data/lib/auction_fun_core/contracts/user_context/phone_confirmation_contract.rb +40 -0
- data/lib/auction_fun_core/contracts/user_context/registration_contract.rb +63 -0
- data/lib/auction_fun_core/entities/auction.rb +17 -0
- data/lib/auction_fun_core/entities/bid.rb +10 -0
- data/lib/auction_fun_core/entities/staff.rb +21 -0
- data/lib/auction_fun_core/entities/user.rb +37 -0
- data/lib/auction_fun_core/events/app.rb +27 -0
- data/lib/auction_fun_core/events/listener.rb +89 -0
- data/lib/auction_fun_core/operations/auction_context/create_operation.rb +77 -0
- data/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb +61 -0
- data/lib/auction_fun_core/operations/auction_context/processor/pause_operation.rb +57 -0
- data/lib/auction_fun_core/operations/auction_context/processor/start_operation.rb +84 -0
- data/lib/auction_fun_core/operations/auction_context/processor/unpause_operation.rb +57 -0
- data/lib/auction_fun_core/operations/base.rb +11 -0
- data/lib/auction_fun_core/operations/bid_context/create_bid_closed_operation.rb +64 -0
- data/lib/auction_fun_core/operations/bid_context/create_bid_penny_operation.rb +64 -0
- data/lib/auction_fun_core/operations/bid_context/create_bid_standard_operation.rb +74 -0
- data/lib/auction_fun_core/operations/staff_context/authentication_operation.rb +52 -0
- data/lib/auction_fun_core/operations/staff_context/registration_operation.rb +76 -0
- data/lib/auction_fun_core/operations/user_context/authentication_operation.rb +52 -0
- data/lib/auction_fun_core/operations/user_context/email_confirmation_operation.rb +67 -0
- data/lib/auction_fun_core/operations/user_context/phone_confirmation_operation.rb +67 -0
- data/lib/auction_fun_core/operations/user_context/registration_operation.rb +105 -0
- data/lib/auction_fun_core/relations/auctions.rb +119 -0
- data/lib/auction_fun_core/relations/bids.rb +28 -0
- data/lib/auction_fun_core/relations/staffs.rb +27 -0
- data/lib/auction_fun_core/relations/users.rb +26 -0
- data/lib/auction_fun_core/repos/auction_context/auction_repository.rb +42 -0
- data/lib/auction_fun_core/repos/bid_context/bid_repository.rb +27 -0
- data/lib/auction_fun_core/repos/staff_context/staff_repository.rb +64 -0
- data/lib/auction_fun_core/repos/user_context/user_repository.rb +78 -0
- data/lib/auction_fun_core/services/mail/templates/layout.html.erb +72 -0
- data/lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb +170 -0
- data/lib/auction_fun_core/services/mail/user_context/registration_mailer.rb +25 -0
- data/lib/auction_fun_core/version.rb +8 -0
- data/lib/auction_fun_core/workers/application_job.rb +22 -0
- data/lib/auction_fun_core/workers/operations/auction_context/processor/finish_operation_job.rb +32 -0
- data/lib/auction_fun_core/workers/operations/auction_context/processor/start_operation_job.rb +32 -0
- data/lib/auction_fun_core/workers/services/mail/user_context/registration_mailer_job.rb +40 -0
- data/lib/auction_fun_core.rb +21 -0
- data/lib/tasks/database.rake +87 -0
- data/system/providers/background_job.rb +15 -0
- data/system/providers/core.rb +35 -0
- data/system/providers/db.rb +26 -0
- data/system/providers/events.rb +13 -0
- data/system/providers/logger.rb +11 -0
- data/system/providers/mail.rb +25 -0
- data/system/providers/persistence.rb +18 -0
- data/system/providers/settings.rb +26 -0
- 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
|