auction_fun_core 0.8.5 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +2 -0
  3. data/CHANGELOG.md +48 -0
  4. data/Procfile +1 -0
  5. data/README.md +34 -32
  6. data/Rakefile +1 -1
  7. data/auction_fun_core.gemspec +1 -0
  8. data/db/migrate/20240229143000_create_auctions.rb +1 -0
  9. data/db/seeds.rb +87 -36
  10. data/i18n/en-US/contracts/contracts.en-US.yml +12 -0
  11. data/i18n/en-US/mail/application.en-US.yml +66 -0
  12. data/i18n/en-US/mail/auction_context/post_auction/participant.en-US.yml +13 -0
  13. data/i18n/en-US/mail/auction_context/post_auction/winner.en-US.yml +13 -0
  14. data/i18n/en-US/mail/auction_context/pre_auction/auction_start_reminder.en-US.yml +11 -0
  15. data/i18n/pt-BR/contracts/contracts.pt-BR.yml +12 -0
  16. data/i18n/pt-BR/mail/application.pt-BR.yml +66 -0
  17. data/i18n/pt-BR/mail/auction_context/post_auction/participant.pt-BR.yml +13 -0
  18. data/i18n/pt-BR/mail/auction_context/post_auction/winner.pt-BR.yml +13 -0
  19. data/i18n/pt-BR/mail/auction_context/pre_auction/auction_start_reminder.pt-BR.yml +11 -0
  20. data/i18n/pt-BR/mail/user_context/registration.pt-BR.yml +0 -4
  21. data/lib/auction_fun_core/business/configuration.rb +31 -0
  22. data/lib/auction_fun_core/business/token_generator.rb +19 -1
  23. data/lib/auction_fun_core/contracts/application_contract.rb +9 -1
  24. data/lib/auction_fun_core/contracts/auction_context/create_contract.rb +35 -20
  25. data/lib/auction_fun_core/contracts/auction_context/post_auction/participant_contract.rb +64 -0
  26. data/lib/auction_fun_core/contracts/auction_context/post_auction/winner_contract.rb +62 -0
  27. data/lib/auction_fun_core/contracts/auction_context/pre_auction/auction_start_reminder_contract.rb +48 -0
  28. data/lib/auction_fun_core/contracts/auction_context/processor/finish/closed_contract.rb +59 -0
  29. data/lib/auction_fun_core/contracts/auction_context/processor/finish/penny_contract.rb +60 -0
  30. data/lib/auction_fun_core/contracts/auction_context/processor/finish/standard_contract.rb +60 -0
  31. data/lib/auction_fun_core/contracts/auction_context/processor/pause_contract.rb +16 -4
  32. data/lib/auction_fun_core/contracts/auction_context/processor/start_contract.rb +17 -5
  33. data/lib/auction_fun_core/contracts/auction_context/processor/unpause_contract.rb +16 -4
  34. data/lib/auction_fun_core/contracts/bid_context/create_bid_closed_contract.rb +20 -11
  35. data/lib/auction_fun_core/contracts/bid_context/create_bid_penny_contract.rb +18 -9
  36. data/lib/auction_fun_core/contracts/bid_context/create_bid_standard_contract.rb +19 -10
  37. data/lib/auction_fun_core/contracts/staff_context/authentication_contract.rb +18 -4
  38. data/lib/auction_fun_core/contracts/staff_context/registration_contract.rb +20 -8
  39. data/lib/auction_fun_core/contracts/user_context/authentication_contract.rb +18 -4
  40. data/lib/auction_fun_core/contracts/user_context/email_confirmation_contract.rb +17 -2
  41. data/lib/auction_fun_core/contracts/user_context/phone_confirmation_contract.rb +17 -2
  42. data/lib/auction_fun_core/contracts/user_context/registration_contract.rb +26 -8
  43. data/lib/auction_fun_core/entities/auction.rb +52 -2
  44. data/lib/auction_fun_core/entities/bid.rb +3 -2
  45. data/lib/auction_fun_core/entities/staff.rb +15 -2
  46. data/lib/auction_fun_core/entities/user.rb +31 -2
  47. data/lib/auction_fun_core/events/app.rb +8 -2
  48. data/lib/auction_fun_core/events/listener.rb +19 -16
  49. data/lib/auction_fun_core/operations/auction_context/create_operation.rb +37 -11
  50. data/lib/auction_fun_core/operations/auction_context/post_auction/participant_operation.rb +85 -0
  51. data/lib/auction_fun_core/operations/auction_context/post_auction/winner_operation.rb +85 -0
  52. data/lib/auction_fun_core/operations/auction_context/pre_auction/auction_start_reminder_operation.rb +96 -0
  53. data/lib/auction_fun_core/operations/auction_context/processor/finish/closed_operation.rb +172 -0
  54. data/lib/auction_fun_core/operations/auction_context/processor/finish/penny_operation.rb +171 -0
  55. data/lib/auction_fun_core/operations/auction_context/processor/finish/standard_operation.rb +171 -0
  56. data/lib/auction_fun_core/operations/auction_context/processor/pause_operation.rb +36 -1
  57. data/lib/auction_fun_core/operations/auction_context/processor/start_operation.rb +23 -11
  58. data/lib/auction_fun_core/operations/auction_context/processor/unpause_operation.rb +36 -1
  59. data/lib/auction_fun_core/operations/bid_context/create_bid_penny_operation.rb +57 -7
  60. data/lib/auction_fun_core/operations/staff_context/registration_operation.rb +10 -0
  61. data/lib/auction_fun_core/relations/auctions.rb +252 -30
  62. data/lib/auction_fun_core/relations/bids.rb +18 -0
  63. data/lib/auction_fun_core/relations/staffs.rb +1 -1
  64. data/lib/auction_fun_core/repos/auction_context/auction_repository.rb +40 -11
  65. data/lib/auction_fun_core/repos/bid_context/bid_repository.rb +27 -5
  66. data/lib/auction_fun_core/repos/staff_context/staff_repository.rb +63 -21
  67. data/lib/auction_fun_core/repos/user_context/user_repository.rb +69 -25
  68. data/lib/auction_fun_core/services/mail/auction_context/post_auction/participant_mailer.rb +37 -0
  69. data/lib/auction_fun_core/services/mail/auction_context/post_auction/winner_mailer.rb +37 -0
  70. data/lib/auction_fun_core/services/mail/auction_context/pre_auction/auction_start_reminder_mailer.rb +35 -0
  71. data/lib/auction_fun_core/services/mail/templates/auction_context/post_auction/participant.html.erb +173 -0
  72. data/lib/auction_fun_core/services/mail/templates/auction_context/post_auction/winner.html.erb +174 -0
  73. data/lib/auction_fun_core/services/mail/templates/auction_context/pre_auction/auction_start_reminder.html.erb +192 -0
  74. data/lib/auction_fun_core/services/mail/templates/user_context/registration.html.erb +2 -2
  75. data/lib/auction_fun_core/services/mail/user_context/registration_mailer.rb +6 -0
  76. data/lib/auction_fun_core/version.rb +1 -1
  77. data/lib/auction_fun_core/workers/application_job.rb +12 -0
  78. data/lib/auction_fun_core/workers/operations/auction_context/post_auction/participant_operation_job.rb +38 -0
  79. data/lib/auction_fun_core/workers/operations/auction_context/post_auction/winner_operation_job.rb +37 -0
  80. data/lib/auction_fun_core/workers/operations/auction_context/pre_auction/auction_start_reminder_operation_job.rb +44 -0
  81. data/lib/auction_fun_core/workers/operations/auction_context/processor/finish/closed_operation_job.rb +37 -0
  82. data/lib/auction_fun_core/workers/operations/auction_context/processor/finish/penny_operation_job.rb +40 -0
  83. data/lib/auction_fun_core/workers/operations/auction_context/processor/finish/standard_operation_job.rb +38 -0
  84. data/lib/auction_fun_core/workers/operations/auction_context/processor/start_operation_job.rb +6 -3
  85. data/lib/auction_fun_core/workers/services/mail/auction_context/post_auction/participant_mailer_job.rb +56 -0
  86. data/lib/auction_fun_core/workers/services/mail/auction_context/post_auction/winner_mailer_job.rb +57 -0
  87. data/lib/auction_fun_core/workers/services/mail/auction_context/pre_auction/auction_start_reminder_mailer_job.rb +48 -0
  88. data/lib/auction_fun_core/workers/services/mail/user_context/registration_mailer_job.rb +8 -6
  89. data/system/providers/background_job.rb +25 -0
  90. metadata +51 -5
  91. data/lib/auction_fun_core/contracts/auction_context/processor/finish_contract.rb +0 -27
  92. data/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb +0 -61
  93. data/lib/auction_fun_core/workers/operations/auction_context/processor/finish_operation_job.rb +0 -32
@@ -12,7 +12,21 @@ module AuctionFunCore
12
12
  include Import["repos.auction_context.auction_repository"]
13
13
  include Import["contracts.auction_context.processor.unpause_contract"]
14
14
 
15
- # @todo Add custom doc
15
+ ##
16
+ # Executes the unpause operation with the provided attributes.
17
+ #
18
+ # @param attributes [Hash] The attributes for the unpause operation.
19
+ # @option attributes auction_id [Integer] The ID of the auction.
20
+ # @yield [Dry::Matcher::Evaluator] The block to handle the result of the operation.
21
+ # @return [Dry::Matcher::Evaluator] The result of the operation.
22
+ #
23
+ # @example
24
+ # attributes = { auction_id: 123 }
25
+ #
26
+ # AuctionFunCore::Operations::AuctionContext::Processor::UnpauseOperation.call(attributes) do |result|
27
+ # result.success { |auction| puts "Unpaused auction sucessfully! #{auction.to_h}" }
28
+ # result.failure { |failure| puts "Failed to unpause auction: #{failure.errors.to_h}"}
29
+ # end
16
30
  def self.call(attributes, &block)
17
31
  operation = new.call(attributes)
18
32
 
@@ -21,6 +35,27 @@ module AuctionFunCore
21
35
  Dry::Matcher::ResultMatcher.call(operation, &block)
22
36
  end
23
37
 
38
+ ##
39
+ # Performs the unpause of an auction.
40
+ #
41
+ # @param attributes [Hash] The attributes for the unpause operation.
42
+ # @option attributes auction_id [Integer] The ID of the auction.
43
+ # @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure] The result of the operation.
44
+ #
45
+ # @example
46
+ # attributes = { auction_id: 123 }
47
+ #
48
+ # operation = AuctionFunCore::Operations::AuctionContext::Processor::UnpauseOperation.call(attributes)
49
+ #
50
+ # if operation.success?
51
+ # auction = operation.success
52
+ # puts "Unpaused auction sucessfully! #{auction.to_h}"
53
+ # end
54
+ #
55
+ # if operation.failure?
56
+ # failure = operation.failure
57
+ # puts "Failed to unpause auction: #{failure.errors.to_h}"
58
+ # end
24
59
  def call(attributes)
25
60
  attrs = yield validate(attributes)
26
61
 
@@ -7,8 +7,10 @@ module AuctionFunCore
7
7
  # Operation class for create new bids for penny auctions.
8
8
  #
9
9
  class CreateBidPennyOperation < AuctionFunCore::Operations::Base
10
- include Import["contracts.bid_context.create_bid_penny_contract"]
11
10
  include Import["repos.bid_context.bid_repository"]
11
+ include Import["repos.auction_context.auction_repository"]
12
+ include Import["contracts.bid_context.create_bid_penny_contract"]
13
+ include Import["workers.operations.auction_context.processor.finish.penny_operation_job"]
12
14
 
13
15
  # @todo Add custom doc
14
16
  def self.call(attributes, &block)
@@ -21,10 +23,13 @@ module AuctionFunCore
21
23
 
22
24
  # @todo Add custom doc
23
25
  def call(attributes)
24
- values = yield validate(attributes)
26
+ auction, values = yield validate_contract(attributes)
25
27
 
26
28
  bid_repository.transaction do |_t|
27
29
  @bid = yield persist(values)
30
+ updated_auction = yield update_end_auction(auction)
31
+
32
+ yield reschedule_end_auction(updated_auction)
28
33
  yield publish_bid_created(@bid)
29
34
  end
30
35
 
@@ -35,19 +40,42 @@ module AuctionFunCore
35
40
  # of the informed attributes.
36
41
  # @param attrs [Hash] bid attributes
37
42
  # @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure]
38
- def validate(attrs)
43
+ def validate_contract(attrs)
39
44
  contract = create_bid_penny_contract.call(attrs)
40
45
 
41
46
  return Failure(contract.errors.to_h) if contract.failure?
42
47
 
43
- Success(contract.to_h)
48
+ Success([contract.context[:auction], contract.to_h])
49
+ end
50
+
51
+ # Updates the end time of an auction if it has already started.
52
+ #
53
+ # This method checks whether an auction is currently running. If the auction is running,
54
+ # it calculates a new end time based on the current time and the duration specified
55
+ # by the auction's stopwatch. The auction's finish time is then updated in the repository.
56
+ # If the auction has not started, it returns the auction as is without any modifications.
57
+ #
58
+ # @param auction [ROM::Struct::Auction] An instance of Auction to be checked and potentially updated.
59
+ # @return [Dry::Monads::Result::Success<ROM::Struct::Auction>, Dry::Monads::Result::Failure]
60
+ def update_end_auction(auction)
61
+ return Success(auction) unless started_auction?(auction)
62
+
63
+ updated_attributes = {
64
+ finished_at: Time.current + auction.stopwatch.seconds,
65
+ kind: auction.kind,
66
+ status: auction.status
67
+ }
68
+
69
+ updated_auction, _ = auction_repository.update(auction.id, updated_attributes)
70
+
71
+ Success(updated_auction)
44
72
  end
45
73
 
46
74
  # Calls the bid repository class to persist the attributes in the database.
47
75
  # @param result [Hash] Bid validated attributes
48
- # @return [ROM::Struct::Bid]
49
- def persist(result)
50
- Success(bid_repository.create(result))
76
+ # @return [Dry::Monads::Result::Success<ROM::Struct::Bid>, Dry::Monads::Result::Failure]
77
+ def persist(values)
78
+ Success(bid_repository.create(values))
51
79
  end
52
80
 
53
81
  # Triggers the publication of event *bids.created*.
@@ -58,6 +86,28 @@ module AuctionFunCore
58
86
 
59
87
  Success()
60
88
  end
89
+
90
+ # TODO: Added a small delay to perform operations (such as sending broadcasts and/or other operations).
91
+ # Reschedules the end time of an auction's background job if the auction has already started.
92
+ #
93
+ # This method checks if the auction is running. If so, it schedules a background job to
94
+ # execute at the auction's current finish time using the job class defined by the
95
+ # penny_operation_job attribute of the auction. If the auction has not started, it
96
+ # simply returns a Success object with no parameters.
97
+ #
98
+ # @return [Dry::Monads::Result::Success<ROM::Struct::Auction>, Dry::Monads::Result::Success<String>]
99
+ def reschedule_end_auction(auction)
100
+ # binding.pry
101
+ return Success(auction) unless started_auction?(auction)
102
+
103
+ perform_at = auction.finished_at
104
+
105
+ Success(penny_operation_job.class.perform_at(perform_at, auction.id))
106
+ end
107
+
108
+ def started_auction?(auction)
109
+ auction.status == "running"
110
+ end
61
111
  end
62
112
  end
63
113
  end
@@ -21,6 +21,7 @@ module AuctionFunCore
21
21
  # @todo Add custom doc
22
22
  def call(attributes)
23
23
  values = yield validate_contract(attributes)
24
+ values = yield assign_default_values(values)
24
25
  values_with_encrypt_password = yield encrypt_password(values)
25
26
 
26
27
  staff_repository.transaction do |_t|
@@ -44,6 +45,15 @@ module AuctionFunCore
44
45
  Success(contract.to_h)
45
46
  end
46
47
 
48
+ # By default, there can only be a single root staff. All other members have their type set to 'common'.
49
+ # @param attrs [Hash] user attributes
50
+ # @return [Dry::Monads::Result::Success]
51
+ def assign_default_values(attrs)
52
+ attrs[:kind] = "common"
53
+
54
+ Success(attrs)
55
+ end
56
+
47
57
  # Transforms the password attribute, encrypting it to be saved in the database.
48
58
  # @param result [Hash] Staff valid contract attributes
49
59
  # @return [Hash] Valid staff database
@@ -7,13 +7,13 @@ module AuctionFunCore
7
7
  class Auctions < ROM::Relation[:sql]
8
8
  use :pagination, per_page: 10
9
9
 
10
- KINDS = Types::Coercible::String.default("standard").enum("standard", "penny", "closed")
11
- STATUSES = Types::Coercible::String.default("scheduled")
12
- .enum("scheduled", "running", "paused", "canceled", "finished")
10
+ KINDS = Types::Coercible::String.enum("standard", "penny", "closed")
11
+ STATUSES = Types::Coercible::String.enum("scheduled", "running", "paused", "canceled", "finished")
13
12
 
14
13
  schema(:auctions, infer: true) do
15
14
  attribute :id, Types::Integer
16
15
  attribute :staff_id, Types::ForeignKey(:staffs)
16
+ attribute :winner_id, Types::ForeignKey(:users)
17
17
  attribute :title, Types::String
18
18
  attribute :description, Types::String
19
19
  attribute :kind, KINDS
@@ -32,6 +32,7 @@ module AuctionFunCore
32
32
 
33
33
  associations do
34
34
  belongs_to :staff, as: :staff, relation: :staffs
35
+ belongs_to :winner, as: :winner, relation: :users, foreign_key: :winner_id
35
36
  has_many :bids, as: :bids, relation: :bids
36
37
  end
37
38
  end
@@ -39,23 +40,32 @@ module AuctionFunCore
39
40
  struct_namespace Entities
40
41
  auto_struct(true)
41
42
 
43
+ # Retrieves a paginated list of auctions along with related information.
44
+ # By default, it retrieves the first page with 10 auctions per page and includes information for the top 3 bidders.
45
+ #
46
+ # @param page [Integer] The page number to retrieve (default is 1).
47
+ # @param per_page [Integer] The number of auctions per page (default is 10).
48
+ # @param options [Hash] Additional options for customization (default is {bidders_count: 3}).
49
+ # @option options [Integer] :bidders_count The number of top bidders to include in the result (default is 3).
50
+ # @return [Array<Hash>] An array of hashes representing the auctions and related information.
51
+ # @example Retrieve the first page of auctions with 10 per page and include information for the top 3 bidders
52
+ # all_auctions = all
53
+ #
54
+ # @example Retrieve the second page of auctions with 5 per page and include information for the top 5 bidders
55
+ # all_auctions = all(2, 5, { bidders_count: 5 })
56
+ #
57
+ # @example Retrieve the third page of auctions with 15 per page and include information for the top 2 bidders
58
+ # all_auctions = all(3, 15, { bidders_count: 2 })
59
+ #
60
+ # @raise [RuntimeError] if either `page` or `per_page` argument is not an integer.
42
61
  def all(page = 1, per_page = 10, options = {bidders_count: 3})
43
- offset = ((page - 1) * per_page)
44
-
45
- read(all_auctions_with_bid_info(per_page, offset, options))
46
- end
47
-
48
- def info(auction_id, options = {bidders_count: 3})
49
- raise "Invalid argument" unless auction_id.is_a?(Integer)
62
+ raise "Invalid argument" unless page.is_a?(Integer) && per_page.is_a?(Integer)
50
63
 
51
- read(auction_with_bid_info(auction_id, options))
52
- end
53
-
54
- private
64
+ offset = ((page - 1) * per_page)
55
65
 
56
- def auction_with_bid_info(auction_id, options = {bidders_count: 3})
57
- "SELECT a.id, a.title, a.description, a.kind, a.status, a.started_at, a.finished_at, a.stopwatch, a.initial_bid_cents,
58
- (SELECT COUNT(*) FROM (SELECT * FROM bids WHERE bids.auction_id = #{auction_id}) dt) AS total_bids,
66
+ sql = <<-SQL
67
+ SELECT a.id, a.title, a.description, a.kind, a.status, a.started_at, a.finished_at, a.stopwatch, a.initial_bid_cents,
68
+ (SELECT COUNT(*) FROM (SELECT * FROM bids WHERE bids.auction_id = a.id) dt) AS total_bids,
59
69
  CASE
60
70
  WHEN a.kind = 'standard' THEN
61
71
  json_build_object(
@@ -77,16 +87,39 @@ module AuctionFunCore
77
87
  WHEN a.kind = 'closed' THEN
78
88
  json_build_object('minimal', (a.initial_bid_cents + (a.initial_bid_cents * 0.10))::int)
79
89
  END as bids
80
- FROM auctions as a
81
- LEFT JOIN LATERAL (SELECT * FROM bids WHERE auction_id = a.id ORDER BY value_cents DESC LIMIT #{options[:bidders_count]}) as bi ON a.id = bi.auction_id AND a.id = #{auction_id}
82
- LEFT JOIN users ON bi.user_id = users.id AND bi.auction_id = a.id
83
- WHERE a.id = #{auction_id}
84
- GROUP BY a.id"
90
+ FROM auctions as a
91
+ LEFT JOIN LATERAL (SELECT * FROM bids WHERE auction_id = a.id ORDER BY value_cents DESC LIMIT #{options[:bidders_count]}) as bi ON a.id = bi.auction_id
92
+ LEFT JOIN users ON bi.user_id = users.id AND bi.auction_id = a.id
93
+ GROUP BY a.id
94
+ LIMIT #{per_page} OFFSET #{offset}
95
+ SQL
96
+
97
+ read(sql)
85
98
  end
86
99
 
87
- def all_auctions_with_bid_info(per_page, offset, options = {bidders_count: 3})
88
- "SELECT a.id, a.title, a.description, a.kind, a.status, a.started_at, a.finished_at, a.stopwatch, a.initial_bid_cents,
89
- (SELECT COUNT(*) FROM (SELECT * FROM bids WHERE bids.auction_id = a.id) dt) AS total_bids,
100
+ # Retrieves detailed information about a specific auction.
101
+ #
102
+ # @param auction_id [Integer] The ID of the auction to retrieve information for.
103
+ # @param options [Hash] Additional options for customization (default is {bidders_count: 3}).
104
+ # @option options [Integer] :bidders_count The number of top bidders to include in the result (default is 3).
105
+ # @return [Hash] A hash representing the auction and related information.
106
+ #
107
+ # @example Retrieve information for auction with ID 123
108
+ # auction_info = info(123)
109
+ #
110
+ # @example Retrieve information for auction with ID 456 and include information for the top 5 bidders
111
+ # auction_info = info(456, { bidders_count: 5 })
112
+ #
113
+ # @example Retrieve information for auction with ID 789 and include information for the top 2 bidders
114
+ # auction_info = info(789, { bidders_count: 2 })
115
+ #
116
+ # @raise [RuntimeError] if `auction_id` argument is not an integer.
117
+ def info(auction_id, options = {bidders_count: 3})
118
+ raise "Invalid argument" unless auction_id.is_a?(Integer)
119
+
120
+ sql = <<-SQL
121
+ SELECT a.id, a.title, a.description, a.kind, a.status, a.started_at, a.finished_at, a.stopwatch, a.initial_bid_cents,
122
+ (SELECT COUNT(*) FROM (SELECT * FROM bids WHERE bids.auction_id = #{auction_id}) dt) AS total_bids,
90
123
  CASE
91
124
  WHEN a.kind = 'standard' THEN
92
125
  json_build_object(
@@ -108,11 +141,200 @@ module AuctionFunCore
108
141
  WHEN a.kind = 'closed' THEN
109
142
  json_build_object('minimal', (a.initial_bid_cents + (a.initial_bid_cents * 0.10))::int)
110
143
  END as bids
111
- FROM auctions as a
112
- LEFT JOIN LATERAL (SELECT * FROM bids WHERE auction_id = a.id ORDER BY value_cents DESC LIMIT #{options[:bidders_count]}) as bi ON a.id = bi.auction_id
113
- LEFT JOIN users ON bi.user_id = users.id AND bi.auction_id = a.id
114
- GROUP BY a.id
115
- LIMIT #{per_page} OFFSET #{offset}"
144
+ FROM auctions as a
145
+ LEFT JOIN LATERAL (SELECT * FROM bids WHERE auction_id = a.id ORDER BY value_cents DESC LIMIT #{options[:bidders_count]}) as bi ON a.id = bi.auction_id AND a.id = #{auction_id}
146
+ LEFT JOIN users ON bi.user_id = users.id AND bi.auction_id = a.id
147
+ WHERE a.id = #{auction_id}
148
+ GROUP BY a.id
149
+ SQL
150
+
151
+ read(sql)
152
+ end
153
+
154
+ # Retrieves the standard auction winner and other participating bidders for a specified auction.
155
+ #
156
+ # This method queries the database to fetch the winner based on the highest bid and collects an array
157
+ # of other participants who placed bids in the auction, all except for the winner. The method returns
158
+ # structured data that includes the auction ID, winner's ID, total number of bids,
159
+ # and a list of participant IDs.
160
+ #
161
+ # @return [Hash] a hash containing details about the auction, including winner and participants:
162
+ # - :id [Integer] the ID of the auction
163
+ # - :winner_id [Integer] the ID of the winning bidder
164
+ # - :total_bids [Integer] the total number of bids placed in the auction
165
+ # - :participants [Array<Integer>] an array of user IDs of the other participants, excluding the winner
166
+ def load_standard_auction_winners_and_participants(auction_id)
167
+ raise "Invalid argument" unless auction_id.is_a?(Integer)
168
+
169
+ sql = <<-SQL
170
+ SELECT a.id, a.kind, a.status, w.user_id AS winner_id, COALESCE(COUNT(b.id), 0) AS total_bids,
171
+ COALESCE(
172
+ ARRAY_REMOVE(ARRAY_AGG(DISTINCT b.user_id ORDER BY b.user_id), w.user_id), ARRAY[]::INT[]
173
+ ) AS participant_ids
174
+ FROM auctions a
175
+ LEFT JOIN bids b ON a.id = b.auction_id
176
+ LEFT JOIN (
177
+ SELECT auction_id, user_id, MAX(value_cents) AS value_cents
178
+ FROM bids
179
+ WHERE auction_id = #{auction_id}
180
+ GROUP BY auction_id, user_id
181
+ ORDER BY value_cents DESC
182
+ LIMIT 1
183
+ ) AS w ON a.id = w.auction_id
184
+ WHERE a.id = #{auction_id}
185
+ GROUP BY a.id, w.user_id
186
+ SQL
187
+
188
+ read(sql)
189
+ end
190
+
191
+ # Retrieves the penny auction winner and other participating bidders for a specified auction.
192
+ #
193
+ # This method queries the database to fetch the winner based on the latest bid and collects an array
194
+ # of other participants who placed bids in the auction, all except for the winner. The method returns
195
+ # structured data that includes the auction ID, winner's ID, total number of bids,
196
+ # and a list of participant IDs.
197
+ #
198
+ # @return [Hash] a hash containing details about the auction, including winner and participants:
199
+ # - :id [Integer] the ID of the auction
200
+ # - :winner_id [Integer] the ID of the winning bidder
201
+ # - :total_bids [Integer] the total number of bids placed in the auction
202
+ # - :participants [Array<Integer>] an array of user IDs of the other participants, excluding the winner
203
+ def load_penny_auction_winners_and_participants(auction_id)
204
+ raise "Invalid argument" unless auction_id.is_a?(Integer)
205
+
206
+ sql = <<-SQL
207
+ SELECT a.id, a.kind, a.status, w.user_id AS winner_id, COALESCE(COUNT(b.id), 0) AS total_bids,
208
+ COALESCE(
209
+ ARRAY_REMOVE(ARRAY_AGG(DISTINCT b.user_id ORDER BY b.user_id), w.user_id), ARRAY[]::INT[]
210
+ ) AS participant_ids
211
+ FROM auctions a
212
+ LEFT JOIN bids b ON a.id = b.auction_id
213
+ LEFT JOIN (
214
+ SELECT auction_id, user_id
215
+ FROM bids
216
+ WHERE auction_id = #{auction_id}
217
+ ORDER BY bids.created_at DESC
218
+ LIMIT 1
219
+ ) AS w ON a.id = w.auction_id
220
+ WHERE a.id = #{auction_id}
221
+ GROUP BY a.id, w.user_id
222
+ SQL
223
+
224
+ read(sql)
225
+ end
226
+
227
+ # Retrieves the closed auction winner and other participating bidders for a specified auction.
228
+ #
229
+ # This method queries the database to fetch the winner based on the highest bid and collects an array
230
+ # of other participants who placed bids in the auction, all except for the winner. The method returns
231
+ # structured data that includes the auction ID, winner's ID, total number of bids,
232
+ # and a list of participant IDs.
233
+ #
234
+ # @return [Hash] a hash containing details about the auction, including winner and participants:
235
+ # - :id [Integer] the ID of the auction
236
+ # - :winner_id [Integer] the ID of the winning bidder
237
+ # - :total_bids [Integer] the total number of bids placed in the auction
238
+ # - :participants [Array<Integer>] an array of user IDs of the other participants, excluding the winner
239
+ def load_closed_auction_winners_and_participants(auction_id)
240
+ raise "Invalid argument" unless auction_id.is_a?(Integer)
241
+
242
+ sql = <<-SQL
243
+ SELECT a.id, a.kind, a.status, w.user_id AS winner_id, COALESCE(COUNT(b.id), 0) AS total_bids,
244
+ COALESCE(
245
+ ARRAY_REMOVE(ARRAY_AGG(DISTINCT b.user_id ORDER BY b.user_id), w.user_id), ARRAY[]::INT[]
246
+ ) AS participant_ids
247
+ FROM auctions a
248
+ LEFT JOIN bids b ON a.id = b.auction_id
249
+ LEFT JOIN (
250
+ SELECT auction_id, user_id, MAX(value_cents) AS value_cents
251
+ FROM bids
252
+ WHERE auction_id = #{auction_id}
253
+ GROUP BY auction_id, user_id
254
+ ORDER BY value_cents DESC
255
+ LIMIT 1
256
+ ) AS w ON a.id = w.auction_id
257
+ WHERE a.id = #{auction_id}
258
+ GROUP BY a.id, w.user_id
259
+ SQL
260
+
261
+ read(sql)
262
+ end
263
+
264
+ # Loads statistics for the winner of a specific auction.
265
+ #
266
+ # @param auction_id [Integer] The ID of the auction to load statistics for.
267
+ # @param winner_id [Integer] The ID of the winner to load statistics for.
268
+ # @return [Hash] A hash representing the statistics for the winner of the auction.
269
+ #
270
+ # @example Load statistics for the winner of auction with ID 123 and winner with ID 456
271
+ # winner_stats = load_winner_statistics(123, 456)
272
+ #
273
+ # @raise [RuntimeError] if either `auction_id` or `winner_id` arguments are not integers.
274
+ def load_winner_statistics(auction_id, winner_id)
275
+ raise "Invalid argument" unless auction_id.is_a?(Integer) && winner_id.is_a?(Integer)
276
+
277
+ sql = <<-SQL
278
+ SELECT a.id, COUNT(b.id) AS auction_total_bids, MAX(b.value_cents) AS winner_bid,
279
+ date(a.finished_at) as auction_date,
280
+ (SELECT COUNT(*) FROM bids b2
281
+ WHERE b2.auction_id = #{auction_id}
282
+ AND b2.user_id = #{winner_id}
283
+ ) AS winner_total_bids
284
+ FROM auctions a
285
+ LEFT JOIN bids b ON a.id = b.auction_id AND a.id = #{auction_id}
286
+ LEFT JOIN users u ON u.id = b.user_id AND u.id = #{winner_id}
287
+ LEFT JOIN (
288
+ SELECT auction_id, user_id, MAX(value_cents) AS value_cents
289
+ FROM bids
290
+ WHERE auction_id = #{auction_id}
291
+ GROUP BY auction_id, user_id
292
+ ORDER BY value_cents DESC
293
+ LIMIT 1
294
+ ) AS w ON a.id = w.auction_id
295
+ WHERE a.id = #{auction_id}
296
+ GROUP BY a.id
297
+ SQL
298
+
299
+ read(sql)
300
+ end
301
+
302
+ # Loads statistics for a participant in a specific auction.
303
+ #
304
+ # @param auction_id [Integer] The ID of the auction to load statistics for.
305
+ # @param participant_id [Integer] The ID of the participant to load statistics for.
306
+ # @return [Hash] A hash representing the statistics for the participant in the auction.
307
+ #
308
+ # @example Load statistics for the participant with ID 456 in auction with ID 123
309
+ # participant_stats = load_participant_statistics(123, 456)
310
+ #
311
+ # @raise [RuntimeError] if either `auction_id` or `participant_id` arguments are not integers.
312
+ def load_participant_statistics(auction_id, participant_id)
313
+ raise "Invalid argument" unless auction_id.is_a?(Integer) && participant_id.is_a?(Integer)
314
+
315
+ sql = <<-SQL
316
+ SELECT a.id, COUNT(b.id) AS auction_total_bids, MAX(b.value_cents) AS winner_bid,
317
+ date(a.finished_at) as auction_date,
318
+ (SELECT COUNT(*) FROM bids b2
319
+ WHERE b2.auction_id = #{auction_id}
320
+ AND b2.user_id = #{participant_id}
321
+ ) AS winner_total_bids
322
+ FROM auctions a
323
+ LEFT JOIN bids b ON a.id = b.auction_id AND a.id = #{auction_id}
324
+ LEFT JOIN users u ON u.id = b.user_id AND u.id = #{participant_id}
325
+ LEFT JOIN (
326
+ SELECT auction_id, user_id, MAX(value_cents) AS value_cents
327
+ FROM bids
328
+ WHERE auction_id = #{auction_id}
329
+ GROUP BY auction_id, user_id
330
+ ORDER BY value_cents DESC
331
+ LIMIT 1
332
+ ) AS w ON a.id = w.auction_id
333
+ WHERE a.id = #{auction_id}
334
+ GROUP BY a.id
335
+ SQL
336
+
337
+ read(sql)
116
338
  end
117
339
  end
118
340
  end
@@ -23,6 +23,24 @@ module AuctionFunCore
23
23
 
24
24
  struct_namespace Entities
25
25
  auto_struct(true)
26
+
27
+ # Retrieves a list of unique user IDs who have placed bids in a specified auction.
28
+ # A participant in an auction is defined as a user who has placed one or more bids.
29
+ #
30
+ # @param auction_id [Integer] the ID of the auction.
31
+ # @return [Array<Integer>] Returns an array of unique user IDs who have participated in the auction.
32
+ # @raise [RuntimeError] Raises an error if the auction_id is not an integer.
33
+ def participants(auction_id)
34
+ raise "Invalid argument" unless auction_id.is_a?(Integer)
35
+
36
+ sql = <<-SQL
37
+ SELECT COALESCE(ARRAY_AGG(DISTINCT user_id), ARRAY[]::INT[]) AS participant_ids
38
+ FROM bids
39
+ WHERE auction_id = #{auction_id}
40
+ SQL
41
+
42
+ read(sql)
43
+ end
26
44
  end
27
45
  end
28
46
  end
@@ -7,7 +7,7 @@ module AuctionFunCore
7
7
  class Staffs < ROM::Relation[:sql]
8
8
  use :pagination, per_page: 10
9
9
 
10
- STAFF_KINDS = Types::Coercible::String.default("common").enum("root", "common")
10
+ STAFF_KINDS = Types::Coercible::String.enum("root", "common")
11
11
 
12
12
  schema(:staffs, infer: true) do
13
13
  attribute :id, Types::Integer
@@ -3,7 +3,30 @@
3
3
  module AuctionFunCore
4
4
  module Repos
5
5
  module AuctionContext
6
- # SQL repository for auctions.
6
+ # Repository for handling repository operations related to auctions.
7
+ #
8
+ # This repository provides methods to interact with auction data in the database,
9
+ # including creating, updating, deleting, and retrieving auctions.
10
+ #
11
+ # @example
12
+ # auction_repo = AuctionFunCore::Repos::AuctionContext::AuctionRepository.new
13
+ #
14
+ # # Retrieve all auctions
15
+ # all_auctions = auction_repo.all
16
+ #
17
+ # # Get the total number of auctions
18
+ # total_auctions = auction_repo.count
19
+ #
20
+ # # Find an auction by its ID
21
+ # auction = auction_repo.by_id(123)
22
+ #
23
+ # # Find an auction by its ID and raise an error if not found
24
+ # auction = auction_repo.by_id!(123)
25
+ #
26
+ # @see AuctionFunCore::Entities::Auction Struct representing auction data
27
+ # @see https://rom-rb.org/learn/sql/3.3/queries/
28
+ # @see https://api.rom-rb.org/rom-sql/ROM/SQL/Relation/Reading
29
+ #
7
30
  class AuctionRepository < ROM::Repository[:auctions]
8
31
  include Import["container"]
9
32
 
@@ -11,28 +34,34 @@ module AuctionFunCore
11
34
  commands :create, update: :by_pk, delete: :by_pk
12
35
 
13
36
  # Returns all auctions in the database.
14
- # @return [Array<ROM::Struct::Auction>, []]
37
+ # @return [Array<ROM::Struct::Auction>]
15
38
  def all
16
39
  auctions.to_a
17
40
  end
18
41
 
19
- # Returns the total number of auctions in database.
20
- # @return [Integer]
42
+ # Returns the total number of auctions in the database.
43
+ #
44
+ # @return [Integer] Total number of auctions.
45
+ #
21
46
  def count
22
47
  auctions.count
23
48
  end
24
49
 
25
- # Search auction in database by primary key.
26
- # @param id [Integer] Auction ID
27
- # @return [ROM::Struct::Auction, nil]
50
+ # Retrieves an auction from the database by its primary key.
51
+ #
52
+ # @param id [Integer] The ID of the auction to retrieve.
53
+ # @return [ROM::Struct::Auction, nil] The retrieved auction, or nil if not found.
54
+ #
28
55
  def by_id(id)
29
56
  auctions.by_pk(id).one
30
57
  end
31
58
 
32
- # Search auction in database by primary key.
33
- # @param id [Integer] Auction ID
34
- # @raise [ROM::TupleCountMismatchError] if not found on database
35
- # @return [ROM::Struct::Auction]
59
+ # Retrieves an auction from the database by its primary key, raising an error if not found.
60
+ #
61
+ # @param id [Integer] The ID of the auction to retrieve.
62
+ # @raise [ROM::TupleCountMismatchError] if the auction is not found.
63
+ # @return [ROM::Struct::Auction] The retrieved auction.
64
+ #
36
65
  def by_id!(id)
37
66
  auctions.by_pk(id).one!
38
67
  end
@@ -3,21 +3,43 @@
3
3
  module AuctionFunCore
4
4
  module Repos
5
5
  module BidContext
6
- # SQL repository for bids.
6
+ # Repository for handling repository operations related to bids.
7
+ #
8
+ # This repository provides methods to interact with bid data in the database,
9
+ # including creating, updating, deleting, and retrieving bids.
10
+ #
11
+ # @example
12
+ # bid_repo = AuctionFunCore::Repos::BidContext::BidRepository.new
13
+ #
14
+ # # Get the total number of bids
15
+ # total_bids = bid_repo.count
16
+ #
17
+ # # Checks if a bid exists based on the provided conditions.
18
+ # bid_repo.exists?(id: 123)
19
+ #
20
+ # @see AuctionFunCore::Entities::Bid Struct representing bid data
21
+ # @see https://rom-rb.org/learn/sql/3.3/queries/
22
+ # @see https://api.rom-rb.org/rom-sql/ROM/SQL/Relation/Reading
23
+ #
7
24
  class BidRepository < ROM::Repository[:bids]
8
25
  include Import["container"]
9
26
 
10
27
  struct_namespace Entities
11
28
  commands :create, update: :by_pk, delete: :by_pk
12
29
 
13
- # Returns the total number of bids in database.
14
- # @return [Integer]
30
+ # Returns the total number of bids in the database.
31
+ #
32
+ # @return [Integer] Total number of bids.
33
+ #
15
34
  def count
16
35
  bids.count
17
36
  end
18
37
 
19
- # @param conditions [Hash] DSL Dataset
20
- # @return [Boolean]
38
+ # Checks if a bid exists based on the provided conditions.
39
+ #
40
+ # @param conditions [Hash] The conditions to check (DSL Dataset).
41
+ # @return [Boolean] true if a bid exists that matches the conditions, otherwise false.
42
+ #
21
43
  def exists?(conditions)
22
44
  bids.exist?(conditions)
23
45
  end