From ae0d5788803ad315f60abee1db4f87b5f0a96522 Mon Sep 17 00:00:00 2001 From: Ricardo Pacheco Date: Wed, 3 Apr 2024 13:07:43 -0300 Subject: [PATCH] Add seed data (#35) --- CHANGELOG.md | 6 + Gemfile | 2 +- Gemfile.lock | 14 +-- auction_fun_core.gemspec | 4 +- db/seeds.rb | 115 +++++++++++++++++- .../processor/finish_operation.rb | 8 +- lib/auction_fun_core/relations/auctions.rb | 76 ++++++++++++ lib/auction_fun_core/relations/bids.rb | 2 +- .../auction_context/auction_repository.rb | 6 + lib/auction_fun_core/version.rb | 2 +- .../auction_repository_spec.rb | 64 ++++++++++ spec/auction_fun_core_spec.rb | 2 +- 12 files changed, 283 insertions(+), 18 deletions(-) create mode 100644 spec/auction_fun_core/repos/auction_context/auction_repository_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index e611a5e..b975268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## [Unreleased] +## [0.8.5] - 2024-04-03 + +### Added + +- Seed data for rapid develpment + ## [0.8.4] - 2024-03-15 - Adjusting dependencies so that they are automatically loaded by the external Gemfile. diff --git a/Gemfile b/Gemfile index 8445078..e095dbc 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ source "https://rubygems.org" gemspec group :development, :test do - gem "faker", "3.2.3" + gem "faker", "3.3.1" gem "pry", "0.14.2" gem "rspec", "3.13.0" gem "standard", "1.35.1" diff --git a/Gemfile.lock b/Gemfile.lock index d8534b3..75b505e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - auction_fun_core (0.8.4) + auction_fun_core (0.8.5) activesupport (= 7.1.3.2) bcrypt (= 3.1.20) dotenv (= 3.1.0) @@ -13,8 +13,8 @@ PATH idlemailer (= 2.2.0) money (= 6.19.0) pg (= 1.5.6) - phonelib (= 0.8.7) - rake (= 13.1.0) + phonelib (= 0.8.8) + rake (= 13.2.0) rom (= 5.3.0) rom-sql (= 3.6.2) sidekiq (= 7.2.2) @@ -106,7 +106,7 @@ GEM dry-initializer (~> 3.0) dry-schema (>= 1.12, < 2) zeitwerk (~> 2.6) - faker (3.2.3) + faker (3.3.1) i18n (>= 1.8.11, < 2) i18n (1.14.1) concurrent-ruby (~> 1.0) @@ -141,14 +141,14 @@ GEM ast (~> 2.4.1) racc pg (1.5.6) - phonelib (0.8.7) + phonelib (0.8.8) pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) racc (1.7.3) rack (3.0.9.1) rainbow (3.1.1) - rake (13.1.0) + rake (13.2.0) redis-client (0.20.0) connection_pool regexp_parser (2.9.0) @@ -256,7 +256,7 @@ PLATFORMS DEPENDENCIES auction_fun_core! database_cleaner-sequel (= 2.0.2) - faker (= 3.2.3) + faker (= 3.3.1) pry (= 0.14.2) rom-factory (= 0.12.0) rspec (= 3.13.0) diff --git a/auction_fun_core.gemspec b/auction_fun_core.gemspec index 0d05495..1861958 100644 --- a/auction_fun_core.gemspec +++ b/auction_fun_core.gemspec @@ -44,8 +44,8 @@ Gem::Specification.new do |spec| spec.add_dependency "idlemailer", "2.2.0" spec.add_dependency "money", "6.19.0" spec.add_dependency "pg", "1.5.6" - spec.add_dependency "phonelib", "0.8.7" - spec.add_dependency "rake", "13.1.0" + spec.add_dependency "phonelib", "0.8.8" + spec.add_dependency "rake", "13.2.0" spec.add_dependency "rom", "5.3.0" spec.add_dependency "rom-sql", "3.6.2" spec.add_dependency "sidekiq", "7.2.2" diff --git a/db/seeds.rb b/db/seeds.rb index 2760031..e3f2aed 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,3 +1,116 @@ # frozen_string_literal: true -# This file should contain all the record creation needed to seed the development database with its default values. +require "pry" +require "faker" + +I18n.enforce_available_locales = false +Faker::Config.locale = "pt-BR" + +# Constants +STOPWATCH_OPTIONS = [15, 30, 45, 60].freeze + +# Start application +AuctionFunCore::Application.start(:core) + +# Instantiate repos +auction_repository = AuctionFunCore::Repos::AuctionContext::AuctionRepository.new +staff_repository = AuctionFunCore::Repos::StaffContext::StaffRepository.new + +# Create root staff. Create as a regular user using the normal flow and after that +# just change the type directly in the db. + +root_staff_attributes = { + kind: "root", name: "Root Bot", email: "rootbot@auctionfun.net", + phone: Faker::PhoneNumber.unique.cell_phone_in_e164, + password: "password", password_confirmation: "password" +} + +AuctionFunCore::Operations::StaffContext::RegistrationOperation.call(root_staff_attributes) do |result| + result.failure { |failure| raise "Error to create root staff: #{failure}" } + result.success { |root| @root = root } +end +staff_repository.update(@root.id, kind: "root") + +# Add common staff + +common_staff_attributes = { + name: "Staff Bot", email: "staffbot@auctionfun.net", + phone: Faker::PhoneNumber.unique.cell_phone_in_e164, + password: "password", password_confirmation: "password" +} + +AuctionFunCore::Operations::StaffContext::RegistrationOperation.call(common_staff_attributes) do |result| + result.failure { |failure| raise "Error to create common staff: #{failure}" } + result.success { |staff| @staff = staff } +end + +# Create some standard auctions +(1..15).each do |i| + attributes = { + staff_id: @staff.id, title: Faker::Commerce.product_name, description: Faker::Lorem.paragraph_by_chars, + kind: "standard", started_at: i.hour.from_now, finished_at: i.day.from_now, + initial_bid_cents: (i * 100), minimal_bid_cents: (i * 100) + } + AuctionFunCore::Operations::AuctionContext::CreateOperation.call(attributes) do |result| + result.failure { |failure| raise "Error to create standard auction: #{failure}" } + result.success { |auction| puts "Create standard auction with: #{auction.to_h}" } + end +end + +# Create some penny auctions +(1..10).each do |i| + stopwatch = STOPWATCH_OPTIONS.sample + started_at = i.hour.from_now + finished_at = started_at + stopwatch.seconds + + attributes = { + staff_id: @staff.id, title: Faker::Commerce.product_name, description: Faker::Lorem.paragraph_by_chars, + kind: "penny", started_at: started_at, finished_at: finished_at, stopwatch: stopwatch + } + AuctionFunCore::Operations::AuctionContext::CreateOperation.call(attributes) do |result| + result.failure { |failure| raise "Error to create penny auction: #{failure}" } + result.success { |auction| puts "Create penny auction with: #{auction.to_h}" } + end +end + +# Create some closed auctions +(1..3).each do |i| + attributes = { + staff_id: @staff.id, title: Faker::Commerce.product_name, kind: "closed", + started_at: i.hour.from_now, finished_at: i.day.from_now.end_of_day, + initial_bid_cents: (i * 1000) + } + AuctionFunCore::Operations::AuctionContext::CreateOperation.call(attributes) do |result| + result.failure { |failure| raise "Error to create closed auction: #{failure}" } + result.success { |auction| puts "Create closed auction with: #{auction.to_h}" } + end +end + +# Add some users +100.times do + attributes = { + name: Faker::Name.name, email: Faker::Internet.unique.email, + phone: Faker::PhoneNumber.unique.cell_phone_in_e164, password: "password", + password_confirmation: "password" + } + AuctionFunCore::Operations::UserContext::RegistrationOperation.call(attributes) do |result| + result.failure { |failure| raise "Error to create user: #{failure}" } + result.success { |user| puts "Create user with: #{user.to_h}" } + end +end + +# Create some bids +auction_repository.all.each do |auction| + next if auction.id.even? + + bid_params = { + auction_id: auction.id, + user_id: rand(2..100), + value_cents: auction.minimal_bid_cents + (auction.minimal_bid_cents * 0.10) + } + + "AuctionFunCore::Operations::BidContext::CreateBid#{auction.kind.capitalize}Operation".constantize.call(bid_params) do |result| + result.failure { |failure| raise "Error to create bid: #{failure}" } + result.success { |bid| puts "Create bid with: #{bid.to_h}" } + end +end diff --git a/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb b/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb index d4278f9..7d46aef 100644 --- a/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb +++ b/lib/auction_fun_core/operations/auction_context/processor/finish_operation.rb @@ -21,10 +21,10 @@ def self.call(auction_id, &block) Dry::Matcher::ResultMatcher.call(operation, &block) end - # @todo Add more actions - # Generate auction statistics and save in JSONB (statistics field) - # Send email to winner with payment info. - # Send email for bidders with user bid stats + # It only performs the basic processing of completing an auction. + # It just changes the status at the database level and triggers the finished event. + # @param auction_id [Integer] Auction ID + # @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure] def call(auction_id) yield validate(auction_id: auction_id) diff --git a/lib/auction_fun_core/relations/auctions.rb b/lib/auction_fun_core/relations/auctions.rb index 049dabf..e8a9fb9 100644 --- a/lib/auction_fun_core/relations/auctions.rb +++ b/lib/auction_fun_core/relations/auctions.rb @@ -38,6 +38,82 @@ class Auctions < ROM::Relation[:sql] struct_namespace Entities auto_struct(true) + + def all(page = 1, per_page = 10, options = {bidders_count: 3}) + offset = ((page - 1) * per_page) + + read(all_auctions_with_bid_info(per_page, offset, options)) + end + + def info(auction_id, options = {bidders_count: 3}) + raise "Invalid argument" unless auction_id.is_a?(Integer) + + read(auction_with_bid_info(auction_id, options)) + end + + private + + def auction_with_bid_info(auction_id, options = {bidders_count: 3}) + "SELECT a.id, a.title, a.description, a.kind, a.status, a.started_at, a.finished_at, a.stopwatch, a.initial_bid_cents, + (SELECT COUNT(*) FROM (SELECT * FROM bids WHERE bids.auction_id = #{auction_id}) dt) AS total_bids, + CASE + WHEN a.kind = 'standard' THEN + json_build_object( + 'current', a.minimal_bid_cents, + 'minimal', a.minimal_bid_cents, + 'bidders', COALESCE( + json_agg(json_build_object('id', bi.id, 'user_id', users.id, 'name', users.name, 'value', bi.value_cents, 'date', bi.created_at) ORDER BY value_cents DESC) + FILTER (where bi.id IS NOT NULL AND users.id IS NOT NULL), '[]'::json + ) + ) + WHEN a.kind = 'penny' THEN + json_build_object( + 'value', a.initial_bid_cents, + 'bidders', COALESCE( + json_agg(json_build_object('id', bi.id, 'user_id', users.id, 'name', users.name, 'value', bi.value_cents, 'date', bi.created_at) ORDER BY value_cents DESC) + FILTER (where bi.id IS NOT NULL AND users.id IS NOT NULL), '[]'::json + ) + ) + WHEN a.kind = 'closed' THEN + json_build_object('minimal', (a.initial_bid_cents + (a.initial_bid_cents * 0.10))::int) + END as bids + FROM auctions as a + 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} + LEFT JOIN users ON bi.user_id = users.id AND bi.auction_id = a.id + WHERE a.id = #{auction_id} + GROUP BY a.id" + end + + def all_auctions_with_bid_info(per_page, offset, options = {bidders_count: 3}) + "SELECT a.id, a.title, a.description, a.kind, a.status, a.started_at, a.finished_at, a.stopwatch, a.initial_bid_cents, + (SELECT COUNT(*) FROM (SELECT * FROM bids WHERE bids.auction_id = a.id) dt) AS total_bids, + CASE + WHEN a.kind = 'standard' THEN + json_build_object( + 'current', a.minimal_bid_cents, + 'minimal', a.minimal_bid_cents, + 'bidders', COALESCE( + json_agg(json_build_object('id', bi.id, 'user_id', users.id, 'name', users.name, 'value', bi.value_cents, 'date', bi.created_at) ORDER BY value_cents DESC) + FILTER (where bi.id IS NOT NULL AND users.id IS NOT NULL), '[]'::json + ) + ) + WHEN a.kind = 'penny' THEN + json_build_object( + 'value', a.initial_bid_cents, + 'bidders', COALESCE( + json_agg(json_build_object('id', bi.id, 'user_id', users.id, 'name', users.name, 'value', bi.value_cents, 'date', bi.created_at) ORDER BY value_cents DESC) + FILTER (where bi.id IS NOT NULL AND users.id IS NOT NULL), '[]'::json + ) + ) + WHEN a.kind = 'closed' THEN + json_build_object('minimal', (a.initial_bid_cents + (a.initial_bid_cents * 0.10))::int) + END as bids + FROM auctions as a + 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 + LEFT JOIN users ON bi.user_id = users.id AND bi.auction_id = a.id + GROUP BY a.id + LIMIT #{per_page} OFFSET #{offset}" + end end end end diff --git a/lib/auction_fun_core/relations/bids.rb b/lib/auction_fun_core/relations/bids.rb index 0f74b16..2049308 100644 --- a/lib/auction_fun_core/relations/bids.rb +++ b/lib/auction_fun_core/relations/bids.rb @@ -3,7 +3,7 @@ module AuctionFunCore module Relations # SQL relation for bids - # @see https://rom-rb.org/5.2/learn/sql/relations/ + # @see https://rom-rb.org/5.0/learn/sql/relations/ class Bids < ROM::Relation[:sql] use :pagination, per_page: 3 diff --git a/lib/auction_fun_core/repos/auction_context/auction_repository.rb b/lib/auction_fun_core/repos/auction_context/auction_repository.rb index 6cb6ddc..5b48004 100644 --- a/lib/auction_fun_core/repos/auction_context/auction_repository.rb +++ b/lib/auction_fun_core/repos/auction_context/auction_repository.rb @@ -10,6 +10,12 @@ class AuctionRepository < ROM::Repository[:auctions] struct_namespace Entities commands :create, update: :by_pk, delete: :by_pk + # Returns all auctions in the database. + # @return [Array, []] + def all + auctions.to_a + end + # Returns the total number of auctions in database. # @return [Integer] def count diff --git a/lib/auction_fun_core/version.rb b/lib/auction_fun_core/version.rb index c6451ad..88c0395 100644 --- a/lib/auction_fun_core/version.rb +++ b/lib/auction_fun_core/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module AuctionFunCore - VERSION = "0.8.4" + VERSION = "0.8.5" # Required class module is a gem dependency class Version; end diff --git a/spec/auction_fun_core/repos/auction_context/auction_repository_spec.rb b/spec/auction_fun_core/repos/auction_context/auction_repository_spec.rb new file mode 100644 index 0000000..58e2000 --- /dev/null +++ b/spec/auction_fun_core/repos/auction_context/auction_repository_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe AuctionFunCore::Repos::AuctionContext::AuctionRepository, type: :repo do + subject(:repo) { described_class.new } + + describe "#all" do + let!(:auction) { Factory[:auction, :default_standard] } + + it "expect return all auctions" do + expect(repo.all.size).to eq(1) + expect(repo.all.first.id).to eq(auction.id) + end + end + + describe "#count" do + context "when has not auction on repository" do + it "expect return zero" do + expect(repo.count).to be_zero + end + end + + context "when has auctions on repository" do + let!(:auction) { Factory[:auction, :default_standard] } + + it "expect return total" do + expect(repo.count).to eq(1) + end + end + end + + describe "#by_id(id)" do + context "when id is founded on repository" do + let!(:auction) { Factory[:auction, :default_standard] } + + it "expect return rom object" do + expect(repo.by_id(auction.id)).to be_a(AuctionFunCore::Entities::Auction) + end + end + + context "when id is not found on repository" do + it "expect return nil" do + expect(repo.by_id(nil)).to be_nil + end + end + end + + describe "#by_id!(id)" do + context "when id is founded on repository" do + let!(:auction) { Factory[:auction, :default_standard] } + + it "expect return rom object" do + expect(repo.by_id(auction.id)).to be_a(AuctionFunCore::Entities::Auction) + end + end + + context "when id is not found on repository" do + it "expect raise exception" do + expect { repo.by_id!(nil) }.to raise_error(ROM::TupleCountMismatchError) + end + end + end +end diff --git a/spec/auction_fun_core_spec.rb b/spec/auction_fun_core_spec.rb index 5ecbec5..7213020 100644 --- a/spec/auction_fun_core_spec.rb +++ b/spec/auction_fun_core_spec.rb @@ -2,6 +2,6 @@ RSpec.describe AuctionFunCore do it "has a version number" do - expect(AuctionFunCore::VERSION).to eq("0.8.4") + expect(AuctionFunCore::VERSION).to eq("0.8.5") end end