Skip to content

Commit

Permalink
[AF-4] Create auctions (#24)
Browse files Browse the repository at this point in the history
* Create auction resources

* Upgrade CHANGELOG and lib version

* Update gemfile lock
  • Loading branch information
ricardopacheco authored Mar 4, 2024
1 parent 042fd14 commit 5a7b554
Show file tree
Hide file tree
Showing 35 changed files with 1,537 additions and 16 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
## [Unreleased]

## [0.7.0] - 2024-02-29

### Added

- Auction entity and your context to handle auction creation business rules.
- Internal Processor module in auctions to handle specific actions.
- User-defined Data Types (custom types) for postgres.

### Fixed

- `en-US` i18n contract messages.

## [0.6.1] - 2024-02-28

### Added
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
auction_fun_core (0.6.1)
auction_fun_core (0.7.0)

GEM
remote: https://rubygems.org/
Expand Down
13 changes: 13 additions & 0 deletions db/migrate/20240229142933_create_custom_types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

ROM::SQL.migration do
up do
run "CREATE TYPE auction_kinds AS ENUM('standard', 'penny', 'closed')"
run "CREATE TYPE auction_statuses AS ENUM('scheduled', 'running', 'paused', 'canceled', 'finished')"
end

down do
run 'DROP TYPE IF EXISTS "auction_kinds"'
run 'DROP TYPE IF EXISTS "auction_statuses"'
end
end
29 changes: 29 additions & 0 deletions db/migrate/20240229143000_enable_auctions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

ROM::SQL.migration do
change do
create_table(:auctions) do
primary_key :id
foreign_key :staff_id, :staffs, null: false
column :title, String, null: false
column :description, :text
column :kind, :auction_kinds, null: false
column :status, :auction_statuses, null: false
column :started_at, DateTime, null: false
column :finished_at, DateTime
column :stopwatch, Integer, null: false, default: 0
column :initial_bid_cents, Integer, null: false, default: 0
column :initial_bid_currency, String, null: false, default: "USD"
column :minimal_bid_cents, Integer, null: false, default: 0
column :minimal_bid_currency, String, null: false, default: "USD"
column :metadata, :jsonb, null: false, default: "{}"
column :statistics, :jsonb, null: false, default: "{}"

column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end

add_index :auctions, %i[staff_id], name: "idx_admin"
add_index :auctions, %i[kind status], name: "idx_kind_status"
end
end
29 changes: 16 additions & 13 deletions i18n/en-US/contracts/contracts.en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,19 @@ en-US:
default: "length must be %{size}"
range: "length must be within %{size_left} - %{size_right}"
custom:
errors:
default:
taken: "has already been taken"
not_found: "not found"
password_confirmation: "doesn't match password"
login_not_found: "Invalid credentials"
inactive_account: "Your account is suspended or inactive"
macro:
email_format: "need to be a valid email"
login_format: "invalid login"
name_format: "must be between %{min} and %{max} characters"
password_format: "must be between %{min} and %{max} characters"
phone_format: "need to be a valid mobile number"
default:
taken: "has already been taken"
not_found: "not found"
password_confirmation: "doesn't match password"
login_not_found: "Invalid credentials"
inactive_account: "Your account is suspended or inactive"
future: "must be in the future"
macro:
email_format: "need to be a valid email"
login_format: "invalid login"
name_format: "must be between %{min} and %{max} characters"
password_format: "must be between %{min} and %{max} characters"
phone_format: "need to be a valid mobile number"
auction_context:
create:
finished_at: "must be after started time"
4 changes: 4 additions & 0 deletions i18n/pt-BR/contracts/contracts.pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@ pt-BR:
password_confirmation: "não corresponde à senha"
login_not_found: "Credenciais inválidas"
inactive_account: "Sua conta está suspensa ou inativa"
future: "deve ser no futuro"
macro:
email_format: "não é um email válido"
login_format: "login inválido"
name_format: "deve ter entre %{min} e %{max} caracteres"
password_format: "deve ter entre %{min} e %{max} caracteres"
phone_format: "não é um número de celular válido"
auction_context:
create:
finished_at: "deve ser depois da hora de início"
19 changes: 19 additions & 0 deletions lib/auction_fun_core/commands/auction_context/create_auction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module AuctionFunCore
module Commands
module AuctionContext
##
# Abstract base class for insert new tuples on auctions table.
# @abstract
class CreateAuction < ROM::Commands::Create[:sql]
relation :auctions
register_as :create
result :one

use :timestamps
timestamp :created_at, :updated_at
end
end
end
end
15 changes: 15 additions & 0 deletions lib/auction_fun_core/commands/auction_context/delete_auction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module AuctionFunCore
module Commands
module AuctionContext
##
# Abstract base class for removes tuples in auctions table.
# @abstract
class DeleteAuction < ROM::Commands::Delete[:sql]
relation :auctions
register_as :delete
end
end
end
end
18 changes: 18 additions & 0 deletions lib/auction_fun_core/commands/auction_context/update_auction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module AuctionFunCore
module Commands
module AuctionContext
##
# Abstract base class for updates all tuples in its auctions table with new attributes
# @abstract
class UpdateAuction < ROM::Commands::Update[:sql]
relation :auctions
register_as :update

use :timestamps
timestamp :updated_at
end
end
end
end
104 changes: 104 additions & 0 deletions lib/auction_fun_core/contracts/auction_context/create_contract.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# frozen_string_literal: true

module AuctionFunCore
module Contracts
module AuctionContext
# Contract class to create new auctions.
class CreateContract < Contracts::ApplicationContract
KINDS = Relations::Auctions::KINDS.values
REQUIRED_FINISHED_AT = KINDS - ["penny"]
MIN_TITLE_LENGTH = 6
MAX_TITLE_LENGTH = 255
STOPWATCH_MIN_VALUE = 15
STOPWATCH_MAX_VALUE = 60

option :staff_repo, default: proc { Repos::StaffContext::StaffRepository.new }

# @param [Hash] opts Sets an allowed list of parameters, as well as some initial validations.
params do
required(:staff_id).filled(:integer)
required(:title).filled(:string, size?: (MIN_TITLE_LENGTH..MAX_TITLE_LENGTH))
required(:kind).value(included_in?: KINDS)
required(:started_at).filled(:time)
optional(:description)
optional(:finished_at).filled(:time)
optional(:initial_bid_cents).filled(:integer)
optional(:stopwatch).filled(:integer)

# Keys with a blank value are discarded.
before(:value_coercer) do |result|
result.to_h.compact
end

# By default, the minimum bid cents is initially equal to initial_bid_cents.
after(:value_coercer) do |result|
result.update(minimal_bid_cents: result[:initial_bid_cents]) if result[:initial_bid_cents]
end
end

# Validation for staff.
# Validate whether the given staff is valid at the database level.
rule(:staff_id) do |context:|
context[:staff] ||= staff_repo.by_id(value)
key.failure(I18n.t("contracts.errors.custom.default.not_found")) unless context[:staff]
end

# Validation for started_at.
# Validates if the entered date is greater than or equal to the current time.
rule(:started_at) do
key.failure(I18n.t("contracts.errors.custom.default.future")) if key? && value <= Time.current
end

# Specific validation when the auction type is informed and it is not penny,
# it must be mandatory to inform the auction closing date/time
rule(:finished_at, :kind) do
if key?(:kind) && !key?(:finished_at) && REQUIRED_FINISHED_AT.include?(values[:kind])
key.failure(I18n.t("contracts.errors.filled?"))
end
end

# Basic specific validation to check if the auction end time
# is less than or equal to the start time.
rule(:finished_at, :started_at) do
if key?(:finished_at) && (values[:finished_at] <= values[:started_at])
key.failure(I18n.t("contracts.errors.custom.auction_context.create.finished_at"))
end
end

# Validation for initial bid amount.
#
rule(:initial_bid_cents) do
# Must be filled if auction kind is not type penny.
key.failure(I18n.t("contracts.errors.filled?")) if !key? && REQUIRED_FINISHED_AT.include?(values[:kind])

# Must be greater than zero if action kind is not type penny.
if key? && REQUIRED_FINISHED_AT.include?(values[:kind]) && values[:initial_bid_cents] <= 0
key.failure(I18n.t("contracts.errors.gt?", num: 0))
end

# Must be equal to zero if auction kind is type penny.
if key? && values[:kind] == "penny" && !values[:initial_bid_cents].zero?
key.failure(I18n.t("contracts.errors.eql?", left: 0))
end
end

# Validation for stopwatch.
#
rule(:stopwatch) do
# Must be filled if auction kind is type penny.
key.failure(I18n.t("contracts.errors.filled?")) if !key? && values[:kind] == "penny"

# Must be an integer between 15 and 60.
if key? && values[:kind] == "penny" && !value.between?(STOPWATCH_MIN_VALUE, STOPWATCH_MAX_VALUE)
key.failure(
I18n.t(
"contracts.errors.included_in?.arg.range",
list_left: STOPWATCH_MIN_VALUE, list_right: STOPWATCH_MAX_VALUE
)
)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module AuctionFunCore
module Contracts
module AuctionContext
module Processor
##
# Contract class for finish auctions.
#
class FinishContract < Contracts::ApplicationContract
option :auction_repo, default: proc { Repos::AuctionContext::AuctionRepository.new }

params do
required(:auction_id).filled(:integer)
end

# Validation for auction.
# Validates if the auction exists in the database.
rule(:auction_id) do |context:|
context[:auction] ||= auction_repo.by_id(value)
key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:auction]
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module AuctionFunCore
module Contracts
module AuctionContext
module Processor
##
# Contract class for start auctions.
#
class StartContract < Contracts::ApplicationContract
STOPWATCH_MIN_VALUE = 15
STOPWATCH_MAX_VALUE = 60
KINDS = Relations::Auctions::KINDS.values
option :auction_repo, default: proc { Repos::AuctionContext::AuctionRepository.new }

params do
required(:auction_id).filled(:integer)
required(:kind).value(included_in?: KINDS)
optional(:stopwatch).filled(:integer)
end

# Validation for auction.
# Validates if the auction exists in the database.
rule(:auction_id) do |context:|
context[:auction] ||= auction_repo.by_id(value)
key.failure(I18n.t("contracts.errors.custom.not_found")) unless context[:auction]
end

# Validation for stopwatch.
#
rule(:stopwatch) do
# Must be filled if auction kind is type penny.
key.failure(I18n.t("contracts.errors.filled?")) if !key? && values[:kind] == "penny"

# Must be an integer between 15 and 60.
if key? && values[:kind] == "penny" && !value.between?(STOPWATCH_MIN_VALUE, STOPWATCH_MAX_VALUE)
key.failure(
I18n.t("contracts.errors.included_in?.arg.range",
list_left: STOPWATCH_MIN_VALUE, list_right: STOPWATCH_MAX_VALUE)
)
end
end
end
end
end
end
end
17 changes: 17 additions & 0 deletions lib/auction_fun_core/entities/auction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module AuctionFunCore
module Entities
# Auction Relations class. This return simple objects with attribute readers
# to represent data in your auction.
class Auction < ROM::Struct
def initial_bid
Money.new(initial_bid_cents, initial_bid_currency)
end

def minimal_bid
Money.new(minimal_bid_cents, minimal_bid_currency)
end
end
end
end
4 changes: 4 additions & 0 deletions lib/auction_fun_core/events/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class App
# @!parser include Dry::Events::Publisher[:app]
include Dry::Events::Publisher[:app]

register_event("auctions.created")
register_event("auctions.started")
register_event("auctions.finished")

register_event("staffs.authentication")
register_event("staffs.registration")

Expand Down
Loading

0 comments on commit 5a7b554

Please sign in to comment.