From 2b433aa174ea6a2251c2ebf7239df5bdb70de3a1 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Fri, 13 Oct 2023 15:23:44 +0300 Subject: [PATCH 01/45] auction api for mobile app --- Gemfile | 3 + Gemfile.lock | 104 +++++------------- app/controllers/auth/sessions_controller.rb | 3 + app/controllers/offers_controller.rb | 7 ++ app/models/user.rb | 3 +- config/customization.yml.sample | 1 + config/initializers/devise.rb | 7 ++ db/migrate/20231013110924_add_jti_to_users.rb | 6 + db/structure.sql | 1 - 9 files changed, 57 insertions(+), 78 deletions(-) create mode 100644 db/migrate/20231013110924_add_jti_to_users.rb diff --git a/Gemfile b/Gemfile index 09869fe1c..d22564806 100644 --- a/Gemfile +++ b/Gemfile @@ -95,3 +95,6 @@ group :test do gem 'spy' gem 'webmock' end + +gem 'devise-jwt' +gem 'jsonapi-serializer' diff --git a/Gemfile.lock b/Gemfile.lock index 447db40fa..660992e7e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -155,9 +155,21 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + devise-jwt (0.11.0) + devise (~> 4.0) + warden-jwt_auth (~> 0.8) docile (1.4.1) domain_name (0.6.20240107) drb (2.2.3) + dry-auto_inject (1.0.1) + dry-core (~> 1.0) + zeitwerk (~> 2.6) + dry-configurable (1.1.0) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-core (1.0.1) + concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) email_validator (2.2.4) activemodel erb (5.0.2) @@ -176,14 +188,6 @@ GEM faraday-net_http (3.4.1) net-http (>= 0.5.0) ffi (1.17.2) - ffi (1.17.2-aarch64-linux-gnu) - ffi (1.17.2-aarch64-linux-musl) - ffi (1.17.2-arm-linux-gnu) - ffi (1.17.2-arm-linux-musl) - ffi (1.17.2-arm64-darwin) - ffi (1.17.2-x86_64-darwin) - ffi (1.17.2-x86_64-linux-gnu) - ffi (1.17.2-x86_64-linux-musl) ffi-compiler (1.3.2) ffi (>= 1.15.5) rake @@ -196,24 +200,6 @@ GEM google-protobuf (4.33.0) bigdecimal rake (>= 13) - google-protobuf (4.33.0-aarch64-linux-gnu) - bigdecimal - rake (>= 13) - google-protobuf (4.33.0-aarch64-linux-musl) - bigdecimal - rake (>= 13) - google-protobuf (4.33.0-arm64-darwin) - bigdecimal - rake (>= 13) - google-protobuf (4.33.0-x86_64-darwin) - bigdecimal - rake (>= 13) - google-protobuf (4.33.0-x86_64-linux-gnu) - bigdecimal - rake (>= 13) - google-protobuf (4.33.0-x86_64-linux-musl) - bigdecimal - rake (>= 13) hashdiff (1.2.1) hashie (5.0.0) hkdf (0.3.0) @@ -256,13 +242,15 @@ GEM bindata faraday (~> 2.0) faraday-follow_redirects + jsonapi-serializer (2.2.0) + activesupport (>= 4.2) + addressable (~> 2.8) + childprocess (~> 5.0) + logger (~> 1.6) jwt (2.10.2) base64 language_server-protocol (3.17.0.5) launchy (3.1.1) - addressable (~> 2.8) - childprocess (~> 5.0) - logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) letter_opener_web (3.0.0) @@ -320,22 +308,6 @@ GEM nokogiri (1.18.10) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.10-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-musl) - racc (~> 1.4) noticed (1.6.3) http (>= 4.0.0) rails (>= 5.2.0) @@ -369,12 +341,6 @@ GEM racc pdfkit (0.8.7.3) pg (1.6.2) - pg (1.6.2-aarch64-linux) - pg (1.6.2-aarch64-linux-musl) - pg (1.6.2-arm64-darwin) - pg (1.6.2-x86_64-darwin) - pg (1.6.2-x86_64-linux) - pg (1.6.2-x86_64-linux-musl) pg_search (2.3.7) activerecord (>= 6.1) activesupport (>= 6.1) @@ -515,22 +481,6 @@ GEM sass-embedded (1.93.2) google-protobuf (~> 4.31) rake (>= 13) - sass-embedded (1.93.2-aarch64-linux-gnu) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-aarch64-linux-musl) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-arm-linux-gnueabihf) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-arm-linux-musleabihf) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-arm64-darwin) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-x86_64-darwin) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-x86_64-linux-gnu) - google-protobuf (~> 4.31) - sass-embedded (1.93.2-x86_64-linux-musl) - google-protobuf (~> 4.31) scenic (1.9.0) activerecord (>= 4.0.0) railties (>= 4.0.0) @@ -583,6 +533,15 @@ GEM concurrent-ruby (~> 1) warden (1.2.9) rack (>= 2.0.9) + warden-jwt_auth (0.8.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + dry-auto_inject (>= 0.8, < 2) + dry-configurable (>= 0.13, < 2) + jwt (~> 2.1) + railties (>= 6.0.0) + warden (~> 1.2) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -609,16 +568,7 @@ GEM zeitwerk (2.7.3) PLATFORMS - aarch64-linux - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - arm64-darwin ruby - x86_64-darwin - x86_64-linux-gnu - x86_64-linux-musl DEPENDENCIES airbrake @@ -637,6 +587,7 @@ DEPENDENCIES delayed_job (~> 4.1.0) delayed_job_active_record devise (~> 4.9.3) + devise-jwt directo! faker faraday (>= 2.14.0) @@ -648,6 +599,7 @@ DEPENDENCIES i18n-debug jbuilder (~> 2.11) jsbundling-rails + jsonapi-serializer jwt letter_opener (~> 1.8) letter_opener_web (~> 3.0) diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 84e7577d1..23b900be3 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -2,6 +2,9 @@ module Auth class SessionsController < Devise::SessionsController include InvalidUserDataHelper after_action :set_invalid_data_flag_in_session, only: [:create] + + skip_before_action :verify_authenticity_token + respond_to :html, :json def create super diff --git a/app/controllers/offers_controller.rb b/app/controllers/offers_controller.rb index ab15b2589..870762449 100644 --- a/app/controllers/offers_controller.rb +++ b/app/controllers/offers_controller.rb @@ -6,6 +6,8 @@ class OffersController < ApplicationController before_action :set_offer, only: %i[show edit update destroy] before_action :authorize_offer_for_user, except: %i[new index create delete] + respond_to :html, :json + include RecaptchaValidatable recaptcha_action 'offer' @@ -46,6 +48,11 @@ def create def index offers = Offer.highest_per_auction_for_user(current_user.id).search(params) @pagy, @offers = pagy(offers, items: params[:per_page] ||= 15) + + respond_to do |format| + format.html + format.json { render json: @offers.as_json(include: [:auction, :billing_profile]) } + end end # GET /offers/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b diff --git a/app/models/user.rb b/app/models/user.rb index 03a9b0ed0..ac3b74c1e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,6 +3,7 @@ class User < ApplicationRecord include Bannable include ReferenceNo + include Devise::JWT::RevocationStrategies::JTIMatcher PARTICIPANT_ROLE = 'participant'.freeze ADMINISTATOR_ROLE = 'administrator'.freeze @@ -12,7 +13,7 @@ class User < ApplicationRecord TARA_PROVIDER = 'tara'.freeze devise :database_authenticatable, :recoverable, :rememberable, :validatable, :confirmable, - :timeoutable + :timeoutable, :jwt_authenticatable, jwt_revocation_strategy: self alias_attribute :country_code, :alpha_two_country_code diff --git a/config/customization.yml.sample b/config/customization.yml.sample index 89192daff..6cd9918fa 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -21,6 +21,7 @@ default: &default mobile_sms_sent_time_limit_in_minutes: 1 auction_filter_available: false ended_auctions_link_available: false + jwt_secret: '' mailer: # Host to which links from emails should redirect to diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b00090533..7788ac1e9 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -29,6 +29,13 @@ def skip_format? config.navigational_formats = ['*/*', :html, :turbo_stream] +config.jwt do |jwt| + jwt.secret = AuctionCenter::Application.config.customization[:jwt_secret] + jwt.dispatch_requests = [ ['POST', %r{^/login$}] ] + jwt.revocation_requests = [ ['DELETE', %r{^/logout$}] ] + jwt.expiration_time = 30.minutes.to_i +end + config.warden do |manager| manager.failure_app = TurboFailureApp # manager.intercept_401 = false diff --git a/db/migrate/20231013110924_add_jti_to_users.rb b/db/migrate/20231013110924_add_jti_to_users.rb new file mode 100644 index 000000000..cf4d56a7b --- /dev/null +++ b/db/migrate/20231013110924_add_jti_to_users.rb @@ -0,0 +1,6 @@ +class AddJtiToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :jti, :string + add_index :users, :jti, unique: true + end +end diff --git a/db/structure.sql b/db/structure.sql index 50070abff..10ce18ffb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -3203,4 +3203,3 @@ INSERT INTO "schema_migrations" (version) VALUES ('20180919104523'), ('20180907083511'), ('20180829130641'); - From 419956e7a054d5be34f834a34552a06e37b3604d Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Mon, 27 Nov 2023 10:47:42 +0200 Subject: [PATCH 02/45] fixed websocket issue --- .../auctions/update_list_broadcast_service.rb | 2 ++ app/channels/auctions_api_channel.rb | 11 +++++++++++ app/controllers/api/stream_names_controller.rb | 12 ++++++++++++ config/routes.rb | 4 ++++ 4 files changed, 29 insertions(+) create mode 100644 app/channels/auctions_api_channel.rb create mode 100644 app/controllers/api/stream_names_controller.rb diff --git a/app/broadcasts/auctions/update_list_broadcast_service.rb b/app/broadcasts/auctions/update_list_broadcast_service.rb index 680658ff7..4dacd0293 100644 --- a/app/broadcasts/auctions/update_list_broadcast_service.rb +++ b/app/broadcasts/auctions/update_list_broadcast_service.rb @@ -25,6 +25,8 @@ def post_call locals: { auction:, user:, updated: participants.include?(user) } end + ActionCable.server.broadcast('auctions_api', { auction: }) + broadcast_later 'auctions', 'auctions/streams/updated_list', locals: { auction:, user: nil, updated: false } diff --git a/app/channels/auctions_api_channel.rb b/app/channels/auctions_api_channel.rb new file mode 100644 index 000000000..a292cde43 --- /dev/null +++ b/app/channels/auctions_api_channel.rb @@ -0,0 +1,11 @@ +class AuctionsApiChannel < ApplicationCable::Channel + def subscribed + Rails.logger.info 'Client subscribed to AuctionsChannel' + stream_from 'auctions_api' + end + + def unsubscribed + Rails.logger.info 'Client unsubscribed from AuctionsChannel' + # Any cleanup needed when channel is unsubscribed + end +end diff --git a/app/controllers/api/stream_names_controller.rb b/app/controllers/api/stream_names_controller.rb new file mode 100644 index 000000000..c7610dfbe --- /dev/null +++ b/app/controllers/api/stream_names_controller.rb @@ -0,0 +1,12 @@ +module Api + class StreamNamesController < ApplicationController + skip_before_action :verify_authenticity_token + + def show + stream_name = 'auctions' + signed_stream_name = ActionCable.server.pubsub.signed_stream_identifier(stream_name) + + render json: { signed_stream_name: } + end + end +end diff --git a/config/routes.rb b/config/routes.rb index b27a451cc..ba346fd6f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,10 @@ end # mount ActionCable.server => '/cable' + namespace :api do + resource :stream_name, only: :show + end + get 'unsubscribe/unsubscribe' patch 'unsubscribe/update' root to: 'auctions#index' From 7b286c97ff93a17f7d56169ae56d7cd17d19e631 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Mon, 4 Dec 2023 12:45:13 +0200 Subject: [PATCH 03/45] updated broadcast --- app/controllers/api/stream_names_controller.rb | 5 +++-- app/views/auctions/index.json.jbuilder | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/stream_names_controller.rb b/app/controllers/api/stream_names_controller.rb index c7610dfbe..ffb8ba531 100644 --- a/app/controllers/api/stream_names_controller.rb +++ b/app/controllers/api/stream_names_controller.rb @@ -3,8 +3,9 @@ class StreamNamesController < ApplicationController skip_before_action :verify_authenticity_token def show - stream_name = 'auctions' - signed_stream_name = ActionCable.server.pubsub.signed_stream_identifier(stream_name) + # stream_name = 'auctions' + # signed_stream_name = ActionCable.server.signed_stream_identifier(stream_name) + signed_stream_name = Turbo::StreamsChannel.signed_stream_name 'auctions' render json: { signed_stream_name: } end diff --git a/app/views/auctions/index.json.jbuilder b/app/views/auctions/index.json.jbuilder index 2c8ebbc4f..b846a1005 100644 --- a/app/views/auctions/index.json.jbuilder +++ b/app/views/auctions/index.json.jbuilder @@ -4,4 +4,7 @@ json.array! @auctions_list do |auction| json.ends_at auction.ends_at.utc json.auction_type auction&.platform json.id auction.uuid + json.highest_bid auction.currently_winning_offer&.price.to_f + json.highest_bidder auction.currently_winning_offer&.username + json.auction_type auction&.platform end From 33f2e4203e780dfab5f7e5475ef7202ec54d3f4b Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Mon, 11 Dec 2023 11:15:08 +0200 Subject: [PATCH 04/45] updated --- app/controllers/api/offers_controller.rb | 35 ++++++++++++++++++++ app/controllers/application_controller.rb | 5 +++ app/controllers/auth/sessions_controller.rb | 4 ++- app/controllers/concerns/rack_session_fix.rb | 15 +++++++++ app/controllers/english_offers_controller.rb | 2 ++ app/controllers/offers_controller.rb | 5 ++- config/application.rb | 17 ++++++++++ config/initializers/devise.rb | 4 +-- config/routes.rb | 1 + 9 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 app/controllers/api/offers_controller.rb create mode 100644 app/controllers/concerns/rack_session_fix.rb diff --git a/app/controllers/api/offers_controller.rb b/app/controllers/api/offers_controller.rb new file mode 100644 index 000000000..3eb5a8ded --- /dev/null +++ b/app/controllers/api/offers_controller.rb @@ -0,0 +1,35 @@ +module Api + class OffersController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def create + # Offer offer = Offer( + # auctionId: widget.auction.uuid, + # cents: _bidAmount!.toInt(), + # userId: user.uuid, + # ); + + # offerBloc.add(AddOfferEvent(offer: offer, authToken: user.tempTokenStore!, auctionType: widget.auction.type!)); + # Navigator.of(context).pop(); + puts '=====' + puts params + puts '=====' + + auction = Auction.find_by(uuid: params[:auctionId]) + + offer = Offer.new + offer.price = params[:cents] + + puts '=====' + puts auction.inspect + puts offer.inspect + puts current_user.inspect + puts '=====' + + render json: { status: 'ok' }, status: :ok + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 797f126ef..49f0844c8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,8 +4,13 @@ class ApplicationController < ActionController::Base helper_method :turbo_frame_request? protect_from_forgery with: :exception +<<<<<<< HEAD before_action :clear_flash, :store_user_location!, if: :storable_location? before_action :set_locale +======= + before_action :set_locale, :clear_flash, :store_user_location!, if: :storable_location? + +>>>>>>> 544562fb (updated) before_action :notifications_for_header content_security_policy do |policy| diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 23b900be3..d194186df 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -1,8 +1,10 @@ module Auth class SessionsController < Devise::SessionsController + include RackSessionFix include InvalidUserDataHelper + after_action :set_invalid_data_flag_in_session, only: [:create] - + skip_before_action :verify_authenticity_token respond_to :html, :json diff --git a/app/controllers/concerns/rack_session_fix.rb b/app/controllers/concerns/rack_session_fix.rb new file mode 100644 index 000000000..c062d6156 --- /dev/null +++ b/app/controllers/concerns/rack_session_fix.rb @@ -0,0 +1,15 @@ +module RackSessionFix + extend ActiveSupport::Concern + class FakeRackSession < Hash + def enabled? + false + end + end + included do + before_action :set_fake_rack_session_for_devise + private + def set_fake_rack_session_for_devise + request.env['rack.session'] ||= FakeRackSession.new + end + end +end diff --git a/app/controllers/english_offers_controller.rb b/app/controllers/english_offers_controller.rb index fb14eaf44..5563bc387 100644 --- a/app/controllers/english_offers_controller.rb +++ b/app/controllers/english_offers_controller.rb @@ -10,6 +10,8 @@ class EnglishOffersController < ApplicationController # order is important include EnglishOffers::Offerable + skip_before_action :verify_authenticity_token + include RecaptchaValidatable recaptcha_action 'english_offer' include OfferNotifable diff --git a/app/controllers/offers_controller.rb b/app/controllers/offers_controller.rb index 870762449..45d3e6c92 100644 --- a/app/controllers/offers_controller.rb +++ b/app/controllers/offers_controller.rb @@ -4,7 +4,10 @@ class OffersController < ApplicationController include Offerable before_action :set_offer, only: %i[show edit update destroy] - before_action :authorize_offer_for_user, except: %i[new index create delete] + before_action :check_for_ban, only: :create + before_action :authorize_phone_confirmation + before_action :authorize_offer_for_user, except: %i[new index create] + skip_before_action :verify_authenticity_token respond_to :html, :json diff --git a/config/application.rb b/config/application.rb index 0d547d8f3..72ca33cc5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,6 +9,22 @@ module AuctionCenter class Application < Rails::Application + + class RequestLoggerMiddleware + def initialize(app) + @app = app + end + + def call(env) + Rails.logger.info "Request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}" + Rails.logger.info "Headers: #{env.select { |k, v| k.start_with? 'HTTP_' }}" + Rails.logger.info "Body: #{env['rack.input'].read}" + env['rack.input'].rewind + + @app.call(env) + end + end + # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 config.active_support.to_time_preserves_timezone = :zone @@ -18,6 +34,7 @@ class Application < Rails::Application config.active_model.i18n_customize_full_message = true config.autoload_paths += %W(#{config.root}/app/models/concerns) + config.middleware.insert_before 0, RequestLoggerMiddleware # config.autoload_paths += Dir[Rails.root.join('app', 'presenters', '**/')] # config.autoload_paths += Dir[Rails.root.join('app', 'broadcasts', '**/')] diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 7788ac1e9..b2d8cc6fc 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -31,8 +31,8 @@ def skip_format? config.jwt do |jwt| jwt.secret = AuctionCenter::Application.config.customization[:jwt_secret] - jwt.dispatch_requests = [ ['POST', %r{^/login$}] ] - jwt.revocation_requests = [ ['DELETE', %r{^/logout$}] ] + jwt.dispatch_requests = [ ['POST', %r{^/sessions/sign_in$}] ] + jwt.revocation_requests = [ ['DELETE', %r{^/sessions/sign_out$}] ] jwt.expiration_time = 30.minutes.to_i end diff --git a/config/routes.rb b/config/routes.rb index ba346fd6f..c4ef52ef5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ namespace :api do resource :stream_name, only: :show + resource :offers, only: :create end get 'unsubscribe/unsubscribe' From dabceca5a3f8943fe4e951b4310bcdfad2bddbb5 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Fri, 29 Dec 2023 10:10:29 +0200 Subject: [PATCH 05/45] updated websocket --- .../auctions/update_list_broadcast_service.rb | 13 +++++- app/controllers/api/offers_controller.rb | 40 +++++++++++-------- .../billing_profiles/index.json.jbuilder | 11 +++++ 3 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 app/views/billing_profiles/index.json.jbuilder diff --git a/app/broadcasts/auctions/update_list_broadcast_service.rb b/app/broadcasts/auctions/update_list_broadcast_service.rb index 4dacd0293..0a871056e 100644 --- a/app/broadcasts/auctions/update_list_broadcast_service.rb +++ b/app/broadcasts/auctions/update_list_broadcast_service.rb @@ -25,7 +25,18 @@ def post_call locals: { auction:, user:, updated: participants.include?(user) } end - ActionCable.server.broadcast('auctions_api', { auction: }) + + auction_json = { + domain_name: auction.domain_name, + starts_at: auction.starts_at, + ends_at: auction.ends_at, + id: auction.uuid, + highest_bid: auction.currently_winning_offer&.price.to_f, + highest_bidder: auction.currently_winning_offer&.username, + auction_type: auction&.platform + } + + ActionCable.server.broadcast('auctions_api', { auction: auction_json }) broadcast_later 'auctions', 'auctions/streams/updated_list', diff --git a/app/controllers/api/offers_controller.rb b/app/controllers/api/offers_controller.rb index 3eb5a8ded..86af8e170 100644 --- a/app/controllers/api/offers_controller.rb +++ b/app/controllers/api/offers_controller.rb @@ -6,30 +6,36 @@ class OffersController < ApplicationController skip_before_action :verify_authenticity_token def create - # Offer offer = Offer( - # auctionId: widget.auction.uuid, - # cents: _bidAmount!.toInt(), - # userId: user.uuid, - # ); - - # offerBloc.add(AddOfferEvent(offer: offer, authToken: user.tempTokenStore!, auctionType: widget.auction.type!)); - # Navigator.of(context).pop(); puts '=====' puts params puts '=====' - auction = Auction.find_by(uuid: params[:auctionId]) + auction = Auction.find_by(uuid: params[:bid][:auction_id]) + return if auction.nil? + + offer = auction.offer_from_user(current_user.uuid) - offer = Offer.new - offer.price = params[:cents] + billing_profile = current_user.billing_profiles.find_by(uuid: params[:bid][:billing_profile_id]) + + if offer.nil? + offer = Offer.new( + auction: auction, + user: current_user, + cents: Money.from_amount(params[:bid][:price]).cents, + billing_profile: billing_profile, + username: Username::GenerateUsernameService.new.call + ) + else + offer.cents = Money.from_amount(params[:bid][:price]).cents + end - puts '=====' - puts auction.inspect - puts offer.inspect - puts current_user.inspect - puts '=====' + if offer.save + Auctions::UpdateListBroadcastService.call({ auction: auction }) - render json: { status: 'ok' }, status: :ok + render json: { status: 'ok' }, status: :ok + else + render json: { status: 'error' }, status: :unprocessable_entity + end end end end diff --git a/app/views/billing_profiles/index.json.jbuilder b/app/views/billing_profiles/index.json.jbuilder new file mode 100644 index 000000000..1414faf3d --- /dev/null +++ b/app/views/billing_profiles/index.json.jbuilder @@ -0,0 +1,11 @@ +json.array! @billing_profiles do |billing_profile| + json.id billing_profile.uuid + json.user_id billing_profile.user_id + json.name billing_profile.name + json.vat_code billing_profile.vat_code + json.street billing_profile.street + json.city billing_profile.city + json.state billing_profile.state + json.postal_code billing_profile.postal_code + json.alpha_two_country_code billing_profile.alpha_two_country_code +end From f6a5a8b1034b929b118d141faa74c94311d13a98 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Tue, 2 Jan 2024 10:44:43 +0200 Subject: [PATCH 06/45] changes route paths --- app/controllers/api/offers_controller.rb | 41 ------------------- .../api/stream_names_controller.rb | 13 ------ app/controllers/api/v1/auctions_controller.rb | 23 +++++++++++ .../api/v1/autobiders_controller.rb | 34 +++++++++++++++ app/controllers/api/v1/offers_controller.rb | 39 ++++++++++++++++++ .../api/v1/stream_names_controller.rb | 13 ++++++ config/routes.rb | 8 +++- 7 files changed, 115 insertions(+), 56 deletions(-) delete mode 100644 app/controllers/api/offers_controller.rb delete mode 100644 app/controllers/api/stream_names_controller.rb create mode 100644 app/controllers/api/v1/auctions_controller.rb create mode 100644 app/controllers/api/v1/autobiders_controller.rb create mode 100644 app/controllers/api/v1/offers_controller.rb create mode 100644 app/controllers/api/v1/stream_names_controller.rb diff --git a/app/controllers/api/offers_controller.rb b/app/controllers/api/offers_controller.rb deleted file mode 100644 index 86af8e170..000000000 --- a/app/controllers/api/offers_controller.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Api - class OffersController < ApplicationController - before_action :authenticate_user! - respond_to :json - - skip_before_action :verify_authenticity_token - - def create - puts '=====' - puts params - puts '=====' - - auction = Auction.find_by(uuid: params[:bid][:auction_id]) - return if auction.nil? - - offer = auction.offer_from_user(current_user.uuid) - - billing_profile = current_user.billing_profiles.find_by(uuid: params[:bid][:billing_profile_id]) - - if offer.nil? - offer = Offer.new( - auction: auction, - user: current_user, - cents: Money.from_amount(params[:bid][:price]).cents, - billing_profile: billing_profile, - username: Username::GenerateUsernameService.new.call - ) - else - offer.cents = Money.from_amount(params[:bid][:price]).cents - end - - if offer.save - Auctions::UpdateListBroadcastService.call({ auction: auction }) - - render json: { status: 'ok' }, status: :ok - else - render json: { status: 'error' }, status: :unprocessable_entity - end - end - end -end diff --git a/app/controllers/api/stream_names_controller.rb b/app/controllers/api/stream_names_controller.rb deleted file mode 100644 index ffb8ba531..000000000 --- a/app/controllers/api/stream_names_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Api - class StreamNamesController < ApplicationController - skip_before_action :verify_authenticity_token - - def show - # stream_name = 'auctions' - # signed_stream_name = ActionCable.server.signed_stream_identifier(stream_name) - signed_stream_name = Turbo::StreamsChannel.signed_stream_name 'auctions' - - render json: { signed_stream_name: } - end - end -end diff --git a/app/controllers/api/v1/auctions_controller.rb b/app/controllers/api/v1/auctions_controller.rb new file mode 100644 index 000000000..7a0201c6a --- /dev/null +++ b/app/controllers/api/v1/auctions_controller.rb @@ -0,0 +1,23 @@ +module Api + module V1 + class AuctionsController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def show + @auction = Auction.english.find_by!(uuid: params[:auction_id]) + offer = @auction.offer_from_user(current_user.id) + autobider = current_user.autobiders.find_or_initialize_by(domain_name: @auction.domain_name) + billing_profiles = BillingProfile.accessible_by(current_ability).where(user_id: current_user.id) + + render json: { + offer: offer, + autobider: autobider, + billing_profiles: billing_profiles + }, status: :ok + end + end + end +end diff --git a/app/controllers/api/v1/autobiders_controller.rb b/app/controllers/api/v1/autobiders_controller.rb new file mode 100644 index 000000000..10b6b8567 --- /dev/null +++ b/app/controllers/api/v1/autobiders_controller.rb @@ -0,0 +1,34 @@ +module Api + module V1 + class AutobidersController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def create + autobider = Autobider.find_by(user_id: current_user.id, domain_name: strong_params[:domain_name]) + + print('---- autobider ---- ') + print(params) + print('---- autobider ---- ') + + if autobider.nil? + autobider = Autobider.new(strong_params.merge(user: current_user)) + else + autobider.price = strong_params[:price] + end + + if autobider.save + render json: { status: 'ok' }, status: :ok + else + render json: { status: 'error' }, status: :unprocessable_entity + end + end + + def strong_params + params.require(:autobider).permit(:domain_name, :price) + end + end + end +end diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb new file mode 100644 index 000000000..bbcba123a --- /dev/null +++ b/app/controllers/api/v1/offers_controller.rb @@ -0,0 +1,39 @@ +module Api + module V1 + class OffersController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def create + auction = Auction.find_by(uuid: params[:bid][:auction_id]) + return if auction.nil? + + offer = auction.offer_from_user(current_user.uuid) + + billing_profile = current_user.billing_profiles.find_by(uuid: params[:bid][:billing_profile_id]) + + if offer.nil? + offer = Offer.new( + auction: auction, + user: current_user, + cents: Money.from_amount(params[:bid][:price]).cents, + billing_profile: billing_profile, + username: Username::GenerateUsernameService.new.call + ) + else + offer.cents = Money.from_amount(params[:bid][:price]).cents + end + + if offer.save + Auctions::UpdateListBroadcastService.call({ auction: auction }) + + render json: { status: 'ok' }, status: :ok + else + render json: { status: 'error' }, status: :unprocessable_entity + end + end + end + end +end diff --git a/app/controllers/api/v1/stream_names_controller.rb b/app/controllers/api/v1/stream_names_controller.rb new file mode 100644 index 000000000..fcfa982f5 --- /dev/null +++ b/app/controllers/api/v1/stream_names_controller.rb @@ -0,0 +1,13 @@ +module Api + module V1 + class StreamNamesController < ApplicationController + skip_before_action :verify_authenticity_token + + def show + signed_stream_name = Turbo::StreamsChannel.signed_stream_name 'auctions' + + render json: { signed_stream_name: } + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index c4ef52ef5..d6f3d56fc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,8 +7,12 @@ # mount ActionCable.server => '/cable' namespace :api do - resource :stream_name, only: :show - resource :offers, only: :create + namespace :v1 do + resource :stream_name, only: :show + resource :offers, only: :create + resource :autobiders, only: :create + resource :auctions, only: :show + end end get 'unsubscribe/unsubscribe' From c526b7347cfc9f2d9ba72da4d40d0e85ee1a4899 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Mon, 15 Jan 2024 09:29:46 +0200 Subject: [PATCH 07/45] updated response --- .../auctions/update_list_broadcast_service.rb | 1 + .../api/v1/autobiders_controller.rb | 30 ++++++++++++------- app/controllers/api/v1/offers_controller.rb | 5 ++++ app/views/auctions/index.json.jbuilder | 1 + 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/app/broadcasts/auctions/update_list_broadcast_service.rb b/app/broadcasts/auctions/update_list_broadcast_service.rb index 0a871056e..85f7f745d 100644 --- a/app/broadcasts/auctions/update_list_broadcast_service.rb +++ b/app/broadcasts/auctions/update_list_broadcast_service.rb @@ -33,6 +33,7 @@ def post_call id: auction.uuid, highest_bid: auction.currently_winning_offer&.price.to_f, highest_bidder: auction.currently_winning_offer&.username, + min_bids_step: auction.min_bids_step, auction_type: auction&.platform } diff --git a/app/controllers/api/v1/autobiders_controller.rb b/app/controllers/api/v1/autobiders_controller.rb index 10b6b8567..b9177df3d 100644 --- a/app/controllers/api/v1/autobiders_controller.rb +++ b/app/controllers/api/v1/autobiders_controller.rb @@ -7,27 +7,37 @@ class AutobidersController < ApplicationController skip_before_action :verify_authenticity_token def create - autobider = Autobider.find_by(user_id: current_user.id, domain_name: strong_params[:domain_name]) + @autobider = Autobider.find_by(user_id: current_user.id, id: strong_params[:id]) - print('---- autobider ---- ') - print(params) - print('---- autobider ---- ') - - if autobider.nil? - autobider = Autobider.new(strong_params.merge(user: current_user)) + if @autobider.nil? + @autobider = Autobider.new(strong_params.merge(user: current_user)) else - autobider.price = strong_params[:price] + @autobider.price = strong_params[:price] end - if autobider.save + if @autobider.save + auction = Auction.where(domain_name: @autobider.domain_name).order(:created_at).last + AutobiderService.autobid(auction) unless skip_autobid(auction) + + Auctions::UpdateListBroadcastService.call({ auction: }) + render json: { status: 'ok' }, status: :ok else render json: { status: 'error' }, status: :unprocessable_entity end end + private + + def skip_autobid(auction) + return false if auction.offers.empty? + + offer = auction.offers.order(:updated_at).last + offer.user == @autobider.user + end + def strong_params - params.require(:autobider).permit(:domain_name, :price) + params.require(:autobider).permit(:id, :domain_name, :price) end end end diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index bbcba123a..065f66853 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -29,6 +29,11 @@ def create if offer.save Auctions::UpdateListBroadcastService.call({ auction: auction }) + auction.update_minimum_bid_step(params[:bid][:price].to_f) + + AutobiderService.autobid(auction) + auction.update_ends_at(offer) + render json: { status: 'ok' }, status: :ok else render json: { status: 'error' }, status: :unprocessable_entity diff --git a/app/views/auctions/index.json.jbuilder b/app/views/auctions/index.json.jbuilder index b846a1005..f31dea549 100644 --- a/app/views/auctions/index.json.jbuilder +++ b/app/views/auctions/index.json.jbuilder @@ -6,5 +6,6 @@ json.array! @auctions_list do |auction| json.id auction.uuid json.highest_bid auction.currently_winning_offer&.price.to_f json.highest_bidder auction.currently_winning_offer&.username + json.min_bids_step auction.min_bids_step.to_f json.auction_type auction&.platform end From 53fa8f987e5b16c0a68c23b121730a5029a22450 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Mon, 22 Jan 2024 11:02:40 +0200 Subject: [PATCH 08/45] added new path --- app/controllers/api/v1/invoices_controller.rb | 31 +++++++++++++++++++ config/routes.rb | 1 + 2 files changed, 32 insertions(+) create mode 100644 app/controllers/api/v1/invoices_controller.rb diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb new file mode 100644 index 000000000..130751761 --- /dev/null +++ b/app/controllers/api/v1/invoices_controller.rb @@ -0,0 +1,31 @@ +module Api + module V1 + class InvoicesController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + # rubocop:disable Metrics/AbcSize + def index + @issued_invoices = invoices_list_by_status(Invoice.statuses[:issued]) + @paid_invoices = invoices_list_by_status(Invoice.statuses[:paid]) + @cancelled_payable_invoices = invoices_list_by_status(Invoice.statuses[:cancelled]).with_ban + @cancelled_expired_invoices = invoices_list_by_status(Invoice.statuses[:cancelled]).without_ban + @deposit_paid = current_user.domain_participate_auctions.order(created_at: :desc) + + render json: { issued_invoices: @issued_invoices, paid_invoices: @paid_invoices, + cancelled_payable_invoices: @cancelled_payable_invoices, cancelled_expired_invoices: @cancelled_expired_invoices, deposit_paid: @deposit_paid } + end + + private + + def invoices_list_by_status(status) + Invoice.accessible_by(current_ability) + .where(user_id: current_user.id) + .where(status: status) + .order(due_date: :desc) + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index d6f3d56fc..1c68e3662 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,7 @@ resource :offers, only: :create resource :autobiders, only: :create resource :auctions, only: :show + resources :invoices, only: :index end end From c9baea1edfe38526ab149e0435746f8e877819eb Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Thu, 25 Jan 2024 13:40:54 +0200 Subject: [PATCH 09/45] updated logs --- app/controllers/api/v1/auctions_controller.rb | 2 +- app/controllers/api/v1/offers_controller.rb | 37 ++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/v1/auctions_controller.rb b/app/controllers/api/v1/auctions_controller.rb index 7a0201c6a..c03d21651 100644 --- a/app/controllers/api/v1/auctions_controller.rb +++ b/app/controllers/api/v1/auctions_controller.rb @@ -7,7 +7,7 @@ class AuctionsController < ApplicationController skip_before_action :verify_authenticity_token def show - @auction = Auction.english.find_by!(uuid: params[:auction_id]) + @auction = Auction.find_by!(uuid: params[:auction_id]) offer = @auction.offer_from_user(current_user.id) autobider = current_user.autobiders.find_or_initialize_by(domain_name: @auction.domain_name) billing_profiles = BillingProfile.accessible_by(current_ability).where(user_id: current_user.id) diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index 065f66853..2bd069019 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -6,36 +6,53 @@ class OffersController < ApplicationController skip_before_action :verify_authenticity_token + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength def create + Rails.logger.info('----') + Rails.logger.info(params) + Rails.logger.info('----') + auction = Auction.find_by(uuid: params[:bid][:auction_id]) return if auction.nil? - + offer = auction.offer_from_user(current_user.uuid) - billing_profile = current_user.billing_profiles.find_by(uuid: params[:bid][:billing_profile_id]) - + billing_profile = current_user.billing_profiles.find_by(id: params[:bid][:billing_profile_id]) + + Rails.logger.info('----') + Rails.logger.info(billing_profile.inspect) + Rails.logger.info('----') + if offer.nil? offer = Offer.new( - auction: auction, + auction:, user: current_user, cents: Money.from_amount(params[:bid][:price]).cents, - billing_profile: billing_profile, - username: Username::GenerateUsernameService.new.call + billing_profile:, + username: auction.english? ? Username::GenerateUsernameService.new.call : nil ) else offer.cents = Money.from_amount(params[:bid][:price]).cents end if offer.save - Auctions::UpdateListBroadcastService.call({ auction: auction }) + if auction.english? + Auctions::UpdateListBroadcastService.call({ auction: }) - auction.update_minimum_bid_step(params[:bid][:price].to_f) + auction.update_minimum_bid_step(params[:bid][:price].to_f) - AutobiderService.autobid(auction) - auction.update_ends_at(offer) + AutobiderService.autobid(auction) + auction.update_ends_at(offer) + end render json: { status: 'ok' }, status: :ok else + + Rails.logger.info('----') + Rails.logger.info(offer.errors.inspect) + Rails.logger.info('----') + render json: { status: 'error' }, status: :unprocessable_entity end end From 30efb81a99550cbec2eb7037e0d40af4c90800e8 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Wed, 31 Jan 2024 11:43:52 +0200 Subject: [PATCH 10/45] added profile controller --- .../auctions/update_list_broadcast_service.rb | 6 +++- .../api/v1/profiles/passwords_controller.rb | 32 +++++++++++++++++++ app/controllers/api/v1/profiles_controller.rb | 23 +++++++++++++ app/views/auctions/index.json.jbuilder | 2 ++ config/routes.rb | 5 +++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/profiles/passwords_controller.rb create mode 100644 app/controllers/api/v1/profiles_controller.rb diff --git a/app/broadcasts/auctions/update_list_broadcast_service.rb b/app/broadcasts/auctions/update_list_broadcast_service.rb index 85f7f745d..c10113ce0 100644 --- a/app/broadcasts/auctions/update_list_broadcast_service.rb +++ b/app/broadcasts/auctions/update_list_broadcast_service.rb @@ -16,6 +16,8 @@ def call private + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength def post_call participants = auction.offers.map(&:user) @@ -34,7 +36,9 @@ def post_call highest_bid: auction.currently_winning_offer&.price.to_f, highest_bidder: auction.currently_winning_offer&.username, min_bids_step: auction.min_bids_step, - auction_type: auction&.platform + auction_type: auction&.platform, + enable_deposit: auction.enable_deposit, + requirement_deposit_in_cents: auction.requirement_deposit_in_cents } ActionCable.server.broadcast('auctions_api', { auction: auction_json }) diff --git a/app/controllers/api/v1/profiles/passwords_controller.rb b/app/controllers/api/v1/profiles/passwords_controller.rb new file mode 100644 index 000000000..0ce36489a --- /dev/null +++ b/app/controllers/api/v1/profiles/passwords_controller.rb @@ -0,0 +1,32 @@ +module Api + module V1 + module Profiles + class PasswordsController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def update + puts params_for_update + + if current_user.valid_password?(params[:user][:current_password]) + if current_user.update(params_for_update) + bypass_sign_in(current_user) + render json: current_user, status: :ok + else + Rails.logger.info current_user.errors.inspect + render json: current_user.errors, status: :unprocessable_entity + end + else + render json: { errors: [t('.incorrect_password')] }, status: :unprocessable_entity + end + end + + def params_for_update + params.require(:user).permit(:password, :password_confirmation) + end + end + end + end +end diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb new file mode 100644 index 000000000..48a5c6ee6 --- /dev/null +++ b/app/controllers/api/v1/profiles_controller.rb @@ -0,0 +1,23 @@ +module Api + module V1 + class ProfilesController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def update + if current_user.update(params_for_update) + render json: current_user, status: :ok + else + Rails.logger.info @user.errors.inspect + render json: current_user.errors, status: :unprocessable_entity + end + end + + def params_for_update + params.require(:user).permit(:email, :country_code, :given_names, :surname, :mobile_phone) + end + end + end +end diff --git a/app/views/auctions/index.json.jbuilder b/app/views/auctions/index.json.jbuilder index f31dea549..0dfb4f330 100644 --- a/app/views/auctions/index.json.jbuilder +++ b/app/views/auctions/index.json.jbuilder @@ -8,4 +8,6 @@ json.array! @auctions_list do |auction| json.highest_bidder auction.currently_winning_offer&.username json.min_bids_step auction.min_bids_step.to_f json.auction_type auction&.platform + json.enable_deposit auction.enable_deposit + json.requirement_deposit_in_cents auction.requirement_deposit_in_cents end diff --git a/config/routes.rb b/config/routes.rb index 1c68e3662..ce774b281 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,11 @@ resource :offers, only: :create resource :autobiders, only: :create resource :auctions, only: :show + resource :profiles, only: :update do + scope module: :profiles do + resource :passwords, only: :update + end + end resources :invoices, only: :index end end From c6c5c97eeaa5474f35551fef64e0a54517134756 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Thu, 8 Feb 2024 10:34:18 +0200 Subject: [PATCH 11/45] added billing profile controller for api --- .../api/v1/billing_profiles_controller.rb | 33 +++++++++++++++++++ config/routes.rb | 1 + 2 files changed, 34 insertions(+) create mode 100644 app/controllers/api/v1/billing_profiles_controller.rb diff --git a/app/controllers/api/v1/billing_profiles_controller.rb b/app/controllers/api/v1/billing_profiles_controller.rb new file mode 100644 index 000000000..4c10f1362 --- /dev/null +++ b/app/controllers/api/v1/billing_profiles_controller.rb @@ -0,0 +1,33 @@ +module Api + module V1 + class BillingProfilesController < ApplicationController + before_action :authenticate_user! + respond_to :json + + skip_before_action :verify_authenticity_token + + def index + @billing_profiles = current_user.billing_profiles + + render json: { billing_profiles: @billing_profiles } + end + + def update + @billing_profile = current_user.billing_profiles.find(params[:id]) + + if @billing_profile.update(billing_profile_params) + render json: { billing_profile: @billing_profile } + else + render json: { errors: @billing_profile.errors }, status: 422 + end + end + + private + + def billing_profile_params + params.require(:billing_profile).permit(:name, :vat_code, :street, :city, :state, :postal_code, + :alpha_two_country_code, :uuid) + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index ce774b281..716f64f52 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,6 +18,7 @@ end end resources :invoices, only: :index + resources :billing_profiles, only: %i[index update create destroy] end end From e548e094914a252f16ef0a748a908eea9809efd8 Mon Sep 17 00:00:00 2001 From: Oleg Hasjanov Date: Fri, 16 Feb 2024 13:17:43 +0200 Subject: [PATCH 12/45] added endpoints for create user and billing profile --- .../api/v1/billing_profiles_controller.rb | 16 ++++++++++++ app/controllers/api/v1/profiles_controller.rb | 26 ++++++++++++++++++- config/routes.rb | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/billing_profiles_controller.rb b/app/controllers/api/v1/billing_profiles_controller.rb index 4c10f1362..5589437a8 100644 --- a/app/controllers/api/v1/billing_profiles_controller.rb +++ b/app/controllers/api/v1/billing_profiles_controller.rb @@ -6,6 +6,8 @@ class BillingProfilesController < ApplicationController skip_before_action :verify_authenticity_token + rescue_from ActiveRecord::RecordNotUnique, with: :handle_unique_violation + def index @billing_profiles = current_user.billing_profiles @@ -22,12 +24,26 @@ def update end end + def create + @billing_profile = current_user.billing_profiles.new(billing_profile_params) + + if @billing_profile.save + render json: { billing_profile: @billing_profile } + else + render json: { errors: @billing_profile.errors }, status: 422 + end + end + private def billing_profile_params params.require(:billing_profile).permit(:name, :vat_code, :street, :city, :state, :postal_code, :alpha_two_country_code, :uuid) end + + def handle_unique_violation + render json: { errors: 'A billing profile with the same VAT code for this user already exists.' }, status: 422 + end end end end diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 48a5c6ee6..2ce8d985c 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -1,7 +1,7 @@ module Api module V1 class ProfilesController < ApplicationController - before_action :authenticate_user! + before_action :authenticate_user!, only: %i[update] respond_to :json skip_before_action :verify_authenticity_token @@ -15,6 +15,30 @@ def update end end + def create + puts '----' + puts params_for_create + puts '----' + + user = User.new(params_for_create) + + if user.save + sign_in(User, user) + render json: user, status: :created + else + Rails.logger.info user.errors.inspect + render json: user.errors, status: :unprocessable_entity + end + end + + private + + def params_for_create + params.require(:user).permit(:email, :password, :password_confirmation, :country_code, + :given_names, :surname, :mobile_phone, :accepts_terms_and_conditions, + :locale, :daily_summary, :identity_code) + end + def params_for_update params.require(:user).permit(:email, :country_code, :given_names, :surname, :mobile_phone) end diff --git a/config/routes.rb b/config/routes.rb index 716f64f52..a7d396471 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,7 +12,7 @@ resource :offers, only: :create resource :autobiders, only: :create resource :auctions, only: :show - resource :profiles, only: :update do + resource :profiles, only: %i[update create] do scope module: :profiles do resource :passwords, only: :update end From d2d5ec4da45800fbd4b3595d18f6df6078da85e0 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 13 Mar 2024 13:56:13 +0200 Subject: [PATCH 13/45] added tara auth --- .../api/v1/tara_auth_sessions_controller.rb | 35 +++++++++++++++++++ app/controllers/application_controller.rb | 4 --- config/customization.yml.sample | 7 ++++ config/initializers/omniauth.rb | 1 + config/routes.rb | 1 + 5 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 app/controllers/api/v1/tara_auth_sessions_controller.rb diff --git a/app/controllers/api/v1/tara_auth_sessions_controller.rb b/app/controllers/api/v1/tara_auth_sessions_controller.rb new file mode 100644 index 000000000..fa425c74e --- /dev/null +++ b/app/controllers/api/v1/tara_auth_sessions_controller.rb @@ -0,0 +1,35 @@ +module Api + module V1 + class TaraAuthSessionsController < ApplicationController + before_action :check_for_permission, only: %i[create] + + respond_to :json + + skip_before_action :verify_authenticity_token + + def create + identity = params[:identity_code] + first_name = params[:first_name] + last_name = params[:last_name] + country_code = params[:country_code] + + @user = User.find_by(identity_code: identity, country_code: country_code) + @user.update(given_names: first_name, surname: last_name) if @user.present? + + if @user.present? + sign_in(User, @user) + render json: { message: 'User signed in', user: @user }, status: :ok + else + render json: { error: 'User not found' }, status: :not_found + end + end + + private + + def check_for_permission + # TODO: + true + end + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 49f0844c8..34d283a25 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,13 +4,9 @@ class ApplicationController < ActionController::Base helper_method :turbo_frame_request? protect_from_forgery with: :exception -<<<<<<< HEAD before_action :clear_flash, :store_user_location!, if: :storable_location? before_action :set_locale -======= - before_action :set_locale, :clear_flash, :store_user_location!, if: :storable_location? ->>>>>>> 544562fb (updated) before_action :notifications_for_header content_security_policy do |policy| diff --git a/config/customization.yml.sample b/config/customization.yml.sample index 6cd9918fa..0cdf0c434 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -89,11 +89,18 @@ default: &default password: 'messente_password' tara: + scope: > + openid + idcard + mid + smartid + discovery: true host: 'tara-test.ria.ee' issuer: 'https://tara-test.ria.ee' identifier: 'identifier' secret: 'secret' redirect_uri: 'redirect_url' + tara_keys: '{"kty":"RSA","n":"iVKwG5cTHFx60wYLndRLlzlvH9m2XsVaWh0LQFcvQBCcUMXjCYQRJ22sLjAz6fvig83dWcoKQVanZfzNGAqG_I54LIVT6oUZxFgCA1cyFKELaCqnpzQa3m7CBOklQUV7Z6Dtj1bMJiMIaEv8lzhtKmqkC6o2xjTWIbVCBublwF0DH5SsVdeX-kC4aJtYCbhsuYuzrn4VpR33NuvLxOBPHDVCMYImxlYU337uf6DjmdZMV96ODqP7E9iMS3GWk_MJEzrgLU7_7JiO3OWtkBUNspZ7pgNdIc6OQ5ZASfWsUufS44kt1fNmPqowklHCRNqcnFOx0lc7ya_VlCdXV6Qfew","e":"AQAB"}' keys: kty: 'RSA' kid: 'public:xWbbVoYq9EwMqphp' diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 928538f90..8b40ce3ea 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -11,6 +11,7 @@ # signing_keys = AuctionCenter::Application.config.customization.dig(:tara, :keys).to_json # signing_keys = AuctionCenter::Application.config.customization.dig(:tara, :tara_keys).to_json + issuer = AuctionCenter::Application.config.customization.dig(:tara, :issuer) host = AuctionCenter::Application.config.customization.dig(:tara, :host) identifier = AuctionCenter::Application.config.customization.dig(:tara, :identifier) diff --git a/config/routes.rb b/config/routes.rb index a7d396471..8d79fec51 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,7 @@ resource :stream_name, only: :show resource :offers, only: :create resource :autobiders, only: :create + resource :tara_auth_session, only: :create resource :auctions, only: :show resource :profiles, only: %i[update create] do scope module: :profiles do From d79c7d49d7370736cff53366d0b87cf3c45540ac Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 25 Mar 2024 13:41:02 +0200 Subject: [PATCH 14/45] added callbakc for payment by mobile --- app/controllers/api/v1/offers_controller.rb | 9 +++++++-- .../api/v1/tara_auth_sessions_controller.rb | 6 +++++- app/controllers/mobile_payments_controller.rb | 11 +++++++++++ app/models/auction.rb | 3 +++ app/views/mobile_payments/index.html.erb | 1 + config/initializers/devise.rb | 2 +- config/routes.rb | 8 ++++---- 7 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 app/controllers/mobile_payments_controller.rb create mode 100644 app/views/mobile_payments/index.html.erb diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index 2bd069019..29246248d 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -13,15 +13,20 @@ def create Rails.logger.info(params) Rails.logger.info('----') + #{"bid"=>{"price"=>320.0, "auction_id"=>"2a893210-f1f1-4be8-9d78-b793f1fa0ec6", "billing_profile_id"=>"23"}, + #"controller"=>"api/v1/offers", "action"=>"create", "offer"=>{}} + auction = Auction.find_by(uuid: params[:bid][:auction_id]) return if auction.nil? - offer = auction.offer_from_user(current_user.uuid) - billing_profile = current_user.billing_profiles.find_by(id: params[:bid][:billing_profile_id]) + offer = auction.offer_from_user(current_user.id) + Rails.logger.info('----') + Rails.logger.info(current_user.inspect) Rails.logger.info(billing_profile.inspect) + Rails.logger.info(offer.inspect) Rails.logger.info('----') if offer.nil? diff --git a/app/controllers/api/v1/tara_auth_sessions_controller.rb b/app/controllers/api/v1/tara_auth_sessions_controller.rb index fa425c74e..d075bc672 100644 --- a/app/controllers/api/v1/tara_auth_sessions_controller.rb +++ b/app/controllers/api/v1/tara_auth_sessions_controller.rb @@ -18,7 +18,7 @@ def create if @user.present? sign_in(User, @user) - render json: { message: 'User signed in', user: @user }, status: :ok + render json: { message: 'User signed in', user: @user, token: current_token }, status: :ok else render json: { error: 'User not found' }, status: :not_found end @@ -30,6 +30,10 @@ def check_for_permission # TODO: true end + + def current_token + request.env['warden-jwt_auth.token'] + end end end end diff --git a/app/controllers/mobile_payments_controller.rb b/app/controllers/mobile_payments_controller.rb new file mode 100644 index 000000000..f67094cc5 --- /dev/null +++ b/app/controllers/mobile_payments_controller.rb @@ -0,0 +1,11 @@ +class MobilePaymentsController < ApplicationController + skip_before_action :verify_authenticity_token, only: %i[callback] + + def callback + EisBilling::SendCallbackService.call(reference_number: linkpay_params[:payment_reference]) + + redirect_to mobile_payments_path + end + + def index; end +end diff --git a/app/models/auction.rb b/app/models/auction.rb index a920aca12..ebfdcdd12 100644 --- a/app/models/auction.rb +++ b/app/models/auction.rb @@ -32,10 +32,13 @@ class Auction < ApplicationRecord # rubocop:disable Metrics delegate :size, to: :offers, prefix: true def update_list_broadcast + return if blind? + Auctions::UpdateListBroadcastService.call({ auction: self }) end def update_offer_broadcast + return if blind? Auctions::UpdateOfferBroadcastService.call({ auction: self }) end diff --git a/app/views/mobile_payments/index.html.erb b/app/views/mobile_payments/index.html.erb new file mode 100644 index 000000000..da60662fa --- /dev/null +++ b/app/views/mobile_payments/index.html.erb @@ -0,0 +1 @@ +Payment was paid! you can close the window arrrggg!!! \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index b2d8cc6fc..5145e89cc 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -31,7 +31,7 @@ def skip_format? config.jwt do |jwt| jwt.secret = AuctionCenter::Application.config.customization[:jwt_secret] - jwt.dispatch_requests = [ ['POST', %r{^/sessions/sign_in$}] ] + jwt.dispatch_requests = [ ['POST', %r{^/sessions/sign_in$}], ['POST', %r{^/api/v1/tara_auth_session}] ] jwt.revocation_requests = [ ['DELETE', %r{^/sessions/sign_out$}] ] jwt.expiration_time = 30.minutes.to_i end diff --git a/config/routes.rb b/config/routes.rb index 8d79fec51..39a4fad85 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -94,6 +94,7 @@ match '/auth/tara/cancel', via: %i[get post delete], to: 'auth/tara#cancel', as: :tara_cancel match '/auth/tara/create', via: [:post], to: 'auth/tara#create', as: :tara_create + match '/api/v1/tara_auth_sessions', via: [:post], to: 'api/v1/tara_auth_sessions#create' end devise_for :users, path: 'sessions', @@ -133,10 +134,6 @@ resources :payment_orders, only: %i[new show create], shallow: true, param: :uuid do member do - # get 'return' - # put 'return' - # post 'return' - post 'callback' end end @@ -145,6 +142,9 @@ match '/linkpay_callback', via: %i[get], to: 'linkpay#callback', as: :linkpay_callback match '/linkpay_deposit_callback', via: %i[get], to: 'linkpay#deposit_callback', as: :deposit_callback + match '/mobile_payments_callback', via: %i[get], to: 'mobile_payments#callback', as: :mobile_payments_callback + get '/mobile_payments', to: 'mobile_payments#index', as: :mobile_payments + resource :locale, only: :update resources :offers, only: :index resources :results, only: :show, param: :uuid From 2d3be3dd4c19e0e11a598712ee23c2a86b5e29d0 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 27 Mar 2024 13:12:05 +0200 Subject: [PATCH 15/45] added hmac validation in tara controller --- app/controllers/api/v1/invoices_controller.rb | 19 +++++++++++++++++- .../api/v1/tara_auth_sessions_controller.rb | 20 +++++++++++++------ app/controllers/mobile_payments_controller.rb | 12 +++++++++++ config/customization.yml.sample | 2 ++ config/routes.rb | 3 +++ 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index 130751761..70b9f635d 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -18,12 +18,29 @@ def index cancelled_payable_invoices: @cancelled_payable_invoices, cancelled_expired_invoices: @cancelled_expired_invoices, deposit_paid: @deposit_paid } end + def pay_deposit + auction = Auction.find_by(uuid: params[:id]) + + render json: { errors: 'Auction not found' } and return unless auction + + description = "auction_deposit #{auction.domain_name}, user_uuid #{current_user.uuid}, " \ + "user_email #{current_user.email}" + + response = EisBilling::PayDepositService.call(amount: auction.deposit, + customer_url: mobile_payments_deposit_callback_url, description:) + if response.result? + render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } + else + render json: { errors: response.errors } + end + end + private def invoices_list_by_status(status) Invoice.accessible_by(current_ability) .where(user_id: current_user.id) - .where(status: status) + .where(status:) .order(due_date: :desc) end end diff --git a/app/controllers/api/v1/tara_auth_sessions_controller.rb b/app/controllers/api/v1/tara_auth_sessions_controller.rb index d075bc672..529d58797 100644 --- a/app/controllers/api/v1/tara_auth_sessions_controller.rb +++ b/app/controllers/api/v1/tara_auth_sessions_controller.rb @@ -1,19 +1,25 @@ module Api module V1 class TaraAuthSessionsController < ApplicationController - before_action :check_for_permission, only: %i[create] - respond_to :json skip_before_action :verify_authenticity_token def create + received_hmac = params[:token] + message = Rails.configuration.customization[:mobile_secret_word] + + unless valid_hmac?(received_hmac, message) + render json: { error: 'Invalid HMAC' }, status: :unauthorized + return + end + identity = params[:identity_code] first_name = params[:first_name] last_name = params[:last_name] country_code = params[:country_code] - @user = User.find_by(identity_code: identity, country_code: country_code) + @user = User.find_by(identity_code: identity, country_code:) @user.update(given_names: first_name, surname: last_name) if @user.present? if @user.present? @@ -26,9 +32,11 @@ def create private - def check_for_permission - # TODO: - true + def valid_hmac?(received_hmac, message) + secret_key = Rails.configuration.customization[:mobile_secret_key] + digest = OpenSSL::Digest.new('sha256') + hmac = OpenSSL::HMAC.digest(digest, secret_key, message) + Base64.strict_encode64(hmac) == received_hmac end def current_token diff --git a/app/controllers/mobile_payments_controller.rb b/app/controllers/mobile_payments_controller.rb index f67094cc5..4e924bb84 100644 --- a/app/controllers/mobile_payments_controller.rb +++ b/app/controllers/mobile_payments_controller.rb @@ -7,5 +7,17 @@ def callback redirect_to mobile_payments_path end + def deposit_callback + EisBilling::SendCallbackService.call(reference_number: linkpay_params[:payment_reference]) + + redirect_to mobile_payments_path + end + def index; end + + private + + def linkpay_params + params.permit(:order_reference, :payment_reference) + end end diff --git a/config/customization.yml.sample b/config/customization.yml.sample index 0cdf0c434..715618fc8 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -22,6 +22,8 @@ default: &default auction_filter_available: false ended_auctions_link_available: false jwt_secret: '' + mobile_secret_key: '' + mobile_secret_word: '' mailer: # Host to which links from emails should redirect to diff --git a/config/routes.rb b/config/routes.rb index 39a4fad85..fbf784aec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,8 @@ end end resources :invoices, only: :index + post 'pay_deposit', to: 'invoices#pay_deposit' + resources :billing_profiles, only: %i[index update create destroy] end end @@ -143,6 +145,7 @@ match '/linkpay_deposit_callback', via: %i[get], to: 'linkpay#deposit_callback', as: :deposit_callback match '/mobile_payments_callback', via: %i[get], to: 'mobile_payments#callback', as: :mobile_payments_callback + match '/mobile_payments_deposit_callback', via: %i[get], to: 'mobile_payments#deposit_callback', as: :mobile_payments_deposit_callback get '/mobile_payments', to: 'mobile_payments#index', as: :mobile_payments resource :locale, only: :update From 92b2a86ce86d6f99cd50e5ca17c59f4d75ece856 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 27 Mar 2024 13:15:50 +0200 Subject: [PATCH 16/45] fixed customization ident --- config/customization.yml.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/customization.yml.sample b/config/customization.yml.sample index 715618fc8..939f11f2f 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -102,7 +102,7 @@ default: &default identifier: 'identifier' secret: 'secret' redirect_uri: 'redirect_url' - tara_keys: '{"kty":"RSA","n":"iVKwG5cTHFx60wYLndRLlzlvH9m2XsVaWh0LQFcvQBCcUMXjCYQRJ22sLjAz6fvig83dWcoKQVanZfzNGAqG_I54LIVT6oUZxFgCA1cyFKELaCqnpzQa3m7CBOklQUV7Z6Dtj1bMJiMIaEv8lzhtKmqkC6o2xjTWIbVCBublwF0DH5SsVdeX-kC4aJtYCbhsuYuzrn4VpR33NuvLxOBPHDVCMYImxlYU337uf6DjmdZMV96ODqP7E9iMS3GWk_MJEzrgLU7_7JiO3OWtkBUNspZ7pgNdIc6OQ5ZASfWsUufS44kt1fNmPqowklHCRNqcnFOx0lc7ya_VlCdXV6Qfew","e":"AQAB"}' + tara_keys: '{"kty":"RSA","n":"iVKwG5cTHFx60wYLndRLlzlvH9m2XsVaWh0LQFcvQBCcUMXjCYQRJ22sLjAz6fvig83dWcoKQVanZfzNGAqG_I54LIVT6oUZxFgCA1cyFKELaCqnpzQa3m7CBOklQUV7Z6Dtj1bMJiMIaEv8lzhtKmqkC6o2xjTWIbVCBublwF0DH5SsVdeX-kC4aJtYCbhsuYuzrn4VpR33NuvLxOBPHDVCMYImxlYU337uf6DjmdZMV96ODqP7E9iMS3GWk_MJEzrgLU7_7JiO3OWtkBUNspZ7pgNdIc6OQ5ZASfWsUufS44kt1fNmPqowklHCRNqcnFOx0lc7ya_VlCdXV6Qfew","e":"AQAB"}' keys: kty: 'RSA' kid: 'public:xWbbVoYq9EwMqphp' From 7e9b5f091ede155b1510433d10316d211914b05c Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 12 Apr 2024 13:45:59 +0300 Subject: [PATCH 17/45] added offers endpoint --- app/controllers/api/v1/offers_controller.rb | 27 +++++++++++---------- config/routes.rb | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index 29246248d..09092892a 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -6,16 +6,23 @@ class OffersController < ApplicationController skip_before_action :verify_authenticity_token + def index + offers = Offer.includes(:auction) + .includes(:result) + .where(user_id: current_user) + .order('auctions.ends_at DESC') + + Rails.logger.info '---- offers ----' + Rails.logger.info(offers.inspect) + Rails.logger.info '----' + + # price with tax + render json: offers.as_json(include: [:auction, :billing_profile]) + end + # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength def create - Rails.logger.info('----') - Rails.logger.info(params) - Rails.logger.info('----') - - #{"bid"=>{"price"=>320.0, "auction_id"=>"2a893210-f1f1-4be8-9d78-b793f1fa0ec6", "billing_profile_id"=>"23"}, - #"controller"=>"api/v1/offers", "action"=>"create", "offer"=>{}} - auction = Auction.find_by(uuid: params[:bid][:auction_id]) return if auction.nil? @@ -23,12 +30,6 @@ def create offer = auction.offer_from_user(current_user.id) - Rails.logger.info('----') - Rails.logger.info(current_user.inspect) - Rails.logger.info(billing_profile.inspect) - Rails.logger.info(offer.inspect) - Rails.logger.info('----') - if offer.nil? offer = Offer.new( auction:, diff --git a/config/routes.rb b/config/routes.rb index fbf784aec..b0ad22dbc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,7 +9,7 @@ namespace :api do namespace :v1 do resource :stream_name, only: :show - resource :offers, only: :create + resources :offers, only: [:create, :index] resource :autobiders, only: :create resource :tara_auth_session, only: :create resource :auctions, only: :show From fbeaf95178203b0f7f043e259b091b5bafa7be8d Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 3 May 2024 13:28:30 +0300 Subject: [PATCH 18/45] remove uneccesary comments, updated json response for offers --- app/controllers/api/v1/offers_controller.rb | 15 ++++--- app/controllers/api/v1/profiles_controller.rb | 4 -- app/models/offer.rb | 41 ++++++++++++++++--- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index 09092892a..6eb061cf0 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -8,16 +8,15 @@ class OffersController < ApplicationController def index offers = Offer.includes(:auction) - .includes(:result) - .where(user_id: current_user) - .order('auctions.ends_at DESC') - - Rails.logger.info '---- offers ----' - Rails.logger.info(offers.inspect) - Rails.logger.info '----' + .includes(:result) + .where(user_id: current_user) + .order('auctions.ends_at DESC') # price with tax - render json: offers.as_json(include: [:auction, :billing_profile]) + render json: offers.as_json( + include: %i[auction billing_profile], + methods: %i[auction_status api_price api_total api_bidders] + ) end # rubocop:disable Metrics/AbcSize diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 2ce8d985c..4637d6796 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -16,10 +16,6 @@ def update end def create - puts '----' - puts params_for_create - puts '----' - user = User.new(params_for_create) if user.save diff --git a/app/models/offer.rb b/app/models/offer.rb index 335c8b7b9..01af80d84 100644 --- a/app/models/offer.rb +++ b/app/models/offer.rb @@ -105,12 +105,43 @@ def price=(value) def total return price * (DEFAULT_PRICE_VALUE + billing_profile.vat_rate) if billing_profile.present? - if user&.country_code == 'EE' || user&.country_code.nil? - default_vat = Setting.find_by(code: :estonian_vat_rate).retrieve - else - default_vat = Countries.vat_rate_from_alpha2_code(user.country_code) + default_vat = if user&.country_code == 'EE' || user&.country_code.nil? + Setting.find_by(code: :estonian_vat_rate).retrieve + else + Countries.vat_rate_from_alpha2_code(user.country_code) + end + + price * (DEFAULT_PRICE_VALUE + (Invoice.find_by(result:)&.vat_rate || default_vat)) + end + + def auction_status + return 'you_won' if auction.finished? && result + return 'you_lost' if auction.finished? && auction.result && !result + + if auction.english? + return 'you_are_winning' if auction.currently_winning_offer == self + + return 'you_are_loosing' end - price * (DEFAULT_PRICE_VALUE + (Invoice.find_by(result: result)&.vat_rate || default_vat)) + 'still_in_progress' + end + + def api_total + total.to_d + end + + def api_price + price.to_d + end + + def api_bidders + auction.offers.map do |offer| + { + username: offer.username, + price: offer.price.to_d, + updated_at: offer.updated_at + } + end end end From 2fcb4106bab225ba916844373981d6d58d25a385 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 27 Dec 2024 12:17:52 +0200 Subject: [PATCH 19/45] remove log --- log/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 log/.keep diff --git a/log/.keep b/log/.keep deleted file mode 100644 index e69de29bb..000000000 From 3367680e00dcef26fd285d7ea170a3decd72bc24 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 7 Mar 2025 15:46:06 +0200 Subject: [PATCH 20/45] Changes: Refactored API controllers to inherit from BaseController Removed redundant authenticate_user! before_action filters Added skip_before_action :check_for_authentication for controllers that don't require authentication Updated AuctionsController to use Ruby 3.x hash syntax shorthand Reduced Devise session timeout from 20 to 5 minutes for improved security Fixed Pagy configuration by commenting out problematic settings causing size parameter errors Disabled overflow setting and DEFAULT freezing to prevent configuration conflicts --- app/controllers/api/v1/auctions_controller.rb | 9 ++++----- app/controllers/api/v1/autobiders_controller.rb | 3 +-- app/controllers/api/v1/base_controller.rb | 11 +++++++++++ app/controllers/api/v1/billing_profiles_controller.rb | 3 +-- app/controllers/api/v1/invoices_controller.rb | 3 +-- app/controllers/api/v1/offers_controller.rb | 3 +-- .../api/v1/profiles/passwords_controller.rb | 3 +-- app/controllers/api/v1/profiles_controller.rb | 3 +-- app/controllers/api/v1/stream_names_controller.rb | 3 ++- .../api/v1/tara_auth_sessions_controller.rb | 4 ++-- config/initializers/devise.rb | 2 +- config/initializers/pagy.rb | 3 --- 12 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 app/controllers/api/v1/base_controller.rb diff --git a/app/controllers/api/v1/auctions_controller.rb b/app/controllers/api/v1/auctions_controller.rb index c03d21651..55e83c6f7 100644 --- a/app/controllers/api/v1/auctions_controller.rb +++ b/app/controllers/api/v1/auctions_controller.rb @@ -1,7 +1,6 @@ module Api module V1 - class AuctionsController < ApplicationController - before_action :authenticate_user! + class AuctionsController < BaseController respond_to :json skip_before_action :verify_authenticity_token @@ -13,9 +12,9 @@ def show billing_profiles = BillingProfile.accessible_by(current_ability).where(user_id: current_user.id) render json: { - offer: offer, - autobider: autobider, - billing_profiles: billing_profiles + offer:, + autobider:, + billing_profiles: }, status: :ok end end diff --git a/app/controllers/api/v1/autobiders_controller.rb b/app/controllers/api/v1/autobiders_controller.rb index b9177df3d..0e5700428 100644 --- a/app/controllers/api/v1/autobiders_controller.rb +++ b/app/controllers/api/v1/autobiders_controller.rb @@ -1,7 +1,6 @@ module Api module V1 - class AutobidersController < ApplicationController - before_action :authenticate_user! + class AutobidersController < BaseController respond_to :json skip_before_action :verify_authenticity_token diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb new file mode 100644 index 000000000..e2bf43543 --- /dev/null +++ b/app/controllers/api/v1/base_controller.rb @@ -0,0 +1,11 @@ +module Api + module V1 + class BaseController < ApplicationController + before_action :check_for_authentication + + def check_for_authentication + render json: { errors: 'Unauthorized' }, status: 401 if current_user.nil? + end + end + end +end diff --git a/app/controllers/api/v1/billing_profiles_controller.rb b/app/controllers/api/v1/billing_profiles_controller.rb index 5589437a8..67b6ab5d0 100644 --- a/app/controllers/api/v1/billing_profiles_controller.rb +++ b/app/controllers/api/v1/billing_profiles_controller.rb @@ -1,7 +1,6 @@ module Api module V1 - class BillingProfilesController < ApplicationController - before_action :authenticate_user! + class BillingProfilesController < BaseController respond_to :json skip_before_action :verify_authenticity_token diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index 70b9f635d..222a1f4d1 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -1,7 +1,6 @@ module Api module V1 - class InvoicesController < ApplicationController - before_action :authenticate_user! + class InvoicesController < BaseController respond_to :json skip_before_action :verify_authenticity_token diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index 6eb061cf0..f45bfa292 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -1,7 +1,6 @@ module Api module V1 - class OffersController < ApplicationController - before_action :authenticate_user! + class OffersController < BaseController respond_to :json skip_before_action :verify_authenticity_token diff --git a/app/controllers/api/v1/profiles/passwords_controller.rb b/app/controllers/api/v1/profiles/passwords_controller.rb index 0ce36489a..e09543f26 100644 --- a/app/controllers/api/v1/profiles/passwords_controller.rb +++ b/app/controllers/api/v1/profiles/passwords_controller.rb @@ -1,8 +1,7 @@ module Api module V1 module Profiles - class PasswordsController < ApplicationController - before_action :authenticate_user! + class PasswordsController < BaseController respond_to :json skip_before_action :verify_authenticity_token diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 4637d6796..25bfda760 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -1,7 +1,6 @@ module Api module V1 - class ProfilesController < ApplicationController - before_action :authenticate_user!, only: %i[update] + class ProfilesController < BaseController respond_to :json skip_before_action :verify_authenticity_token diff --git a/app/controllers/api/v1/stream_names_controller.rb b/app/controllers/api/v1/stream_names_controller.rb index fcfa982f5..7af769068 100644 --- a/app/controllers/api/v1/stream_names_controller.rb +++ b/app/controllers/api/v1/stream_names_controller.rb @@ -1,6 +1,7 @@ module Api module V1 - class StreamNamesController < ApplicationController + class StreamNamesController < BaseController + skip_before_action :check_for_authentication skip_before_action :verify_authenticity_token def show diff --git a/app/controllers/api/v1/tara_auth_sessions_controller.rb b/app/controllers/api/v1/tara_auth_sessions_controller.rb index 529d58797..c3bf42daa 100644 --- a/app/controllers/api/v1/tara_auth_sessions_controller.rb +++ b/app/controllers/api/v1/tara_auth_sessions_controller.rb @@ -1,8 +1,8 @@ module Api module V1 - class TaraAuthSessionsController < ApplicationController + class TaraAuthSessionsController < BaseController respond_to :json - + skip_before_action :check_for_authentication skip_before_action :verify_authenticity_token def create diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 5145e89cc..8c97457d9 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -211,7 +211,7 @@ def skip_format? # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this # time the user will be asked for credentials again. Default is 30 minutes. - config.timeout_in = 20.minutes + config.timeout_in = 5.minutes # ==> Configuration for :lockable # Defines which strategy will be used to lock an account. diff --git a/config/initializers/pagy.rb b/config/initializers/pagy.rb index 5914e2ee3..0d35d8772 100644 --- a/config/initializers/pagy.rb +++ b/config/initializers/pagy.rb @@ -7,6 +7,3 @@ { locale: 'et', filepath: "#{Rails.root}/config/locales/pagy.et.yml" }) # default :empty_page (other options :last_page and :exception ) -Pagy::DEFAULT[:overflow] = :last_page -# Pagy::DEFAULT[:size] = [1,2,2,1] -Pagy::DEFAULT.freeze From 9faa9a7fbd50f6dfab80d5c74f8ddbb5f06c6a40 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 21 Mar 2025 15:13:17 +0200 Subject: [PATCH 21/45] added email and password authetication --- app/controllers/api/v1/base_controller.rb | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index e2bf43543..bc7d5dd93 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -2,10 +2,40 @@ module Api module V1 class BaseController < ApplicationController before_action :check_for_authentication + respond_to :json def check_for_authentication + if token = extract_token_from_header + begin + decoded_token = decode_token(token) + payload = decoded_token[0] + + if user_id = payload['sub'] + @current_user = User.find_by(id: user_id) + sign_in(@current_user) if @current_user + end + rescue JWT::DecodeError => e + Rails.logger.error "JWT decode error: #{e.message}" + rescue StandardError => e + Rails.logger.error "Authentication error: #{e.message}" + end + end + render json: { errors: 'Unauthorized' }, status: 401 if current_user.nil? end + + private + + def decode_token(token) + JWT.decode(token, AuctionCenter::Application.config.customization[:jwt_secret], true, { algorithm: 'HS256' }) + end + + def extract_token_from_header + header = request.headers['HTTP_AUTHORIZATION'] + return nil unless header + + header.split(' ').last if header.start_with?('Bearer ') + end end end end From 94e48f9ffab8861fb20d796d9d12ca6cd52bd5af Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 25 Mar 2025 14:37:54 +0200 Subject: [PATCH 22/45] refactor invoice controller --- app/controllers/api/v1/invoices_controller.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index 222a1f4d1..dca31ae83 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -4,6 +4,7 @@ class InvoicesController < BaseController respond_to :json skip_before_action :verify_authenticity_token + before_action :set_auction, only: [:pay_deposit] # rubocop:disable Metrics/AbcSize def index @@ -14,17 +15,16 @@ def index @deposit_paid = current_user.domain_participate_auctions.order(created_at: :desc) render json: { issued_invoices: @issued_invoices, paid_invoices: @paid_invoices, - cancelled_payable_invoices: @cancelled_payable_invoices, cancelled_expired_invoices: @cancelled_expired_invoices, deposit_paid: @deposit_paid } + cancelled_payable_invoices: @cancelled_payable_invoices, + cancelled_expired_invoices: @cancelled_expired_invoices, + deposit_paid: @deposit_paid } end def pay_deposit - auction = Auction.find_by(uuid: params[:id]) - - render json: { errors: 'Auction not found' } and return unless auction + render json: { errors: 'Auction not found' } and return unless @auction description = "auction_deposit #{auction.domain_name}, user_uuid #{current_user.uuid}, " \ "user_email #{current_user.email}" - response = EisBilling::PayDepositService.call(amount: auction.deposit, customer_url: mobile_payments_deposit_callback_url, description:) if response.result? @@ -36,6 +36,10 @@ def pay_deposit private + def set_auction + @auction = Auction.find_by(uuid: params[:id]) + end + def invoices_list_by_status(status) Invoice.accessible_by(current_ability) .where(user_id: current_user.id) From 857a216559458ed85fec0b1accf9f2af592cba51 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 26 Mar 2025 13:41:09 +0200 Subject: [PATCH 23/45] handle deposit --- app/controllers/api/v1/invoices_controller.rb | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index dca31ae83..1ed043c62 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -12,20 +12,32 @@ def index @paid_invoices = invoices_list_by_status(Invoice.statuses[:paid]) @cancelled_payable_invoices = invoices_list_by_status(Invoice.statuses[:cancelled]).with_ban @cancelled_expired_invoices = invoices_list_by_status(Invoice.statuses[:cancelled]).without_ban - @deposit_paid = current_user.domain_participate_auctions.order(created_at: :desc) - - render json: { issued_invoices: @issued_invoices, paid_invoices: @paid_invoices, - cancelled_payable_invoices: @cancelled_payable_invoices, - cancelled_expired_invoices: @cancelled_expired_invoices, - deposit_paid: @deposit_paid } + @deposit_paid = current_user.domain_participate_auctions.includes(:auction).order(created_at: :desc) + + render json: { + issued_invoices: @issued_invoices, + paid_invoices: @paid_invoices, + cancelled_payable_invoices: @cancelled_payable_invoices, + cancelled_expired_invoices: @cancelled_expired_invoices, + deposit_paid: @deposit_paid.map do |dpa| + { + id: dpa.id, + created_at: dpa.created_at, + status: dpa.status, + invoice_number: dpa.invoice_number, + domain_name: dpa.auction.domain_name, + auction_id: dpa.auction_id + } + end + } end def pay_deposit render json: { errors: 'Auction not found' } and return unless @auction - description = "auction_deposit #{auction.domain_name}, user_uuid #{current_user.uuid}, " \ + description = "auction_deposit #{@auction.domain_name}, user_uuid #{current_user.uuid}, " \ "user_email #{current_user.email}" - response = EisBilling::PayDepositService.call(amount: auction.deposit, + response = EisBilling::PayDepositService.call(amount: @auction.deposit, customer_url: mobile_payments_deposit_callback_url, description:) if response.result? render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } From 7b540406c2da07f98ad686856e85faf695f35f4f Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 7 Apr 2025 11:21:23 +0300 Subject: [PATCH 24/45] updated invoices api --- app/controllers/api/v1/invoices_controller.rb | 14 ++++++++++++++ app/controllers/mobile_payments_controller.rb | 2 ++ app/services/eis_billing/send_callback_service.rb | 5 ++++- config/routes.rb | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index 1ed043c62..b3d7f2b00 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -32,6 +32,20 @@ def index } end + def one_off_payment + render json: { errors: 'Auction not found' } and return unless @auction + + response = EisBilling::OneoffService.call(invoice_number: @invoice.number.to_s, + customer_url: mobile_payments_deposit_callback_url, + amount: params[:amount]) + + if response.result? + render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } + else + render json: { errors: response.errors } + end + end + def pay_deposit render json: { errors: 'Auction not found' } and return unless @auction diff --git a/app/controllers/mobile_payments_controller.rb b/app/controllers/mobile_payments_controller.rb index 4e924bb84..f7f02e587 100644 --- a/app/controllers/mobile_payments_controller.rb +++ b/app/controllers/mobile_payments_controller.rb @@ -2,7 +2,9 @@ class MobilePaymentsController < ApplicationController skip_before_action :verify_authenticity_token, only: %i[callback] def callback + Rails.logger.info '------ SendCallbackService ----' EisBilling::SendCallbackService.call(reference_number: linkpay_params[:payment_reference]) + Rails.logger.info '------ SendCallbackService ----' redirect_to mobile_payments_path end diff --git a/app/services/eis_billing/send_callback_service.rb b/app/services/eis_billing/send_callback_service.rb index cf87ea254..da00cdeac 100644 --- a/app/services/eis_billing/send_callback_service.rb +++ b/app/services/eis_billing/send_callback_service.rb @@ -8,7 +8,7 @@ def initialize(reference_number:) end def self.call(reference_number:) - new(reference_number: reference_number).call + new(reference_number:).call end def call @@ -22,6 +22,9 @@ def send_it end def billing_callback_url + Rails.logger.info '------ callback ---- reference_number' + Rails.logger.info "------ callback ---- reference_number: #{reference_number}" + Rails.logger.info '------ callback ---- reference_number' "/api/v1/callback_handler/callback?payment_reference=#{reference_number}" end end diff --git a/config/routes.rb b/config/routes.rb index b0ad22dbc..5fb0ab2f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ end resources :invoices, only: :index post 'pay_deposit', to: 'invoices#pay_deposit' + post 'one_off_payment', to: 'invoices#one_off_payment' resources :billing_profiles, only: %i[index update create destroy] end From 953f8c507da284effe47cfc04af2991ef9bb6958 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 11 Apr 2025 14:55:56 +0300 Subject: [PATCH 25/45] updated websocket data for mobile api --- app/broadcasts/auctions/update_list_broadcast_service.rb | 2 +- app/controllers/api/v1/autobiders_controller.rb | 3 ++- app/views/auctions/index.json.jbuilder | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/broadcasts/auctions/update_list_broadcast_service.rb b/app/broadcasts/auctions/update_list_broadcast_service.rb index c10113ce0..c8c55b6f0 100644 --- a/app/broadcasts/auctions/update_list_broadcast_service.rb +++ b/app/broadcasts/auctions/update_list_broadcast_service.rb @@ -27,7 +27,6 @@ def post_call locals: { auction:, user:, updated: participants.include?(user) } end - auction_json = { domain_name: auction.domain_name, starts_at: auction.starts_at, @@ -35,6 +34,7 @@ def post_call id: auction.uuid, highest_bid: auction.currently_winning_offer&.price.to_f, highest_bidder: auction.currently_winning_offer&.username, + highest_bidder_uuid: auction.currently_winning_offer&.user&.uuid, min_bids_step: auction.min_bids_step, auction_type: auction&.platform, enable_deposit: auction.enable_deposit, diff --git a/app/controllers/api/v1/autobiders_controller.rb b/app/controllers/api/v1/autobiders_controller.rb index 0e5700428..ccbbd0340 100644 --- a/app/controllers/api/v1/autobiders_controller.rb +++ b/app/controllers/api/v1/autobiders_controller.rb @@ -9,9 +9,10 @@ def create @autobider = Autobider.find_by(user_id: current_user.id, id: strong_params[:id]) if @autobider.nil? - @autobider = Autobider.new(strong_params.merge(user: current_user)) + @autobider = Autobider.new(strong_params.merge(user: current_user, enable: true)) else @autobider.price = strong_params[:price] + @autobider.enable = true end if @autobider.save diff --git a/app/views/auctions/index.json.jbuilder b/app/views/auctions/index.json.jbuilder index 0dfb4f330..f24824109 100644 --- a/app/views/auctions/index.json.jbuilder +++ b/app/views/auctions/index.json.jbuilder @@ -6,6 +6,7 @@ json.array! @auctions_list do |auction| json.id auction.uuid json.highest_bid auction.currently_winning_offer&.price.to_f json.highest_bidder auction.currently_winning_offer&.username + json.highest_bidder_uuid auction.currently_winning_offer&.user&.uuid json.min_bids_step auction.min_bids_step.to_f json.auction_type auction&.platform json.enable_deposit auction.enable_deposit From ec7189351fcd562b659b5ec3bc5cfa09ce75a0ac Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 21 Apr 2025 11:03:54 +0300 Subject: [PATCH 26/45] fixed invoices --- .../eis_billing/payment_status_controller.rb | 6 +++--- app/controllers/linkpay_controller.rb | 8 ++++++++ app/models/payment_orders/every_pay.rb | 11 +++++++++-- app/services/eis_billing/send_callback_service.rb | 3 --- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/controllers/eis_billing/payment_status_controller.rb b/app/controllers/eis_billing/payment_status_controller.rb index 7412469a2..f2f43e604 100644 --- a/app/controllers/eis_billing/payment_status_controller.rb +++ b/app/controllers/eis_billing/payment_status_controller.rb @@ -5,7 +5,7 @@ def update unless is_deposit invoice = ::Invoice.find_by(number: params[:order_reference]) - define_payment_option(invoice: invoice) + define_payment_option(invoice:) end respond_to do |format| @@ -31,7 +31,7 @@ def check_for_deposit(params) def define_payment_option(invoice:) if params[:invoice_number_collection].nil? - payment_process(invoice: invoice) unless invoice.nil? + payment_process(invoice:) unless invoice.nil? else pay_mulitply(params[:invoice_number_collection]) end @@ -42,7 +42,7 @@ def pay_mulitply(data) data.each do |d| invoice = ::Invoice.find_by(number: d[:number]) - payment_process(invoice: invoice) + payment_process(invoice:) end end diff --git a/app/controllers/linkpay_controller.rb b/app/controllers/linkpay_controller.rb index 9f1141f39..06b608c25 100644 --- a/app/controllers/linkpay_controller.rb +++ b/app/controllers/linkpay_controller.rb @@ -2,8 +2,16 @@ class LinkpayController < ApplicationController skip_before_action :verify_authenticity_token, only: %i[callback] def callback + Rails.logger.info('=============') + Rails.logger.info(linkpay_params) + Rails.logger.info('=============') + EisBilling::SendCallbackService.call(reference_number: linkpay_params[:payment_reference]) + Rails.logger.info('=============') + Rails.logger.info(' After Send callback') + Rails.logger.info('=============') + redirect_to invoices_path(state: 'payment') end diff --git a/app/models/payment_orders/every_pay.rb b/app/models/payment_orders/every_pay.rb index 42f3db80d..7b3e99742 100644 --- a/app/models/payment_orders/every_pay.rb +++ b/app/models/payment_orders/every_pay.rb @@ -49,6 +49,10 @@ def form_url # Perform necessary checks and mark the invoice as paid def mark_invoice_as_paid + Rails.logger.info('============= settled_payment?') + Rails.logger.info(settled_payment?) + Rails.logger.info('=============') + return unless settled_payment? response.with_indifferent_access @@ -65,6 +69,9 @@ def mark_invoice_as_paid # Check if the intermediary reports payment as settled and we can expect money on # our accounts def settled_payment? + Rails.logger.info('============= IN SETTLED PAYMENT? payment_state') + Rails.logger.info(response) + Rails.logger.info('=============') SUCCESSFUL_PAYMENT.include?(response['payment_state']) end @@ -91,13 +98,13 @@ def base_params api_username: USER, account_id: ACCOUNT_ID, timestamp: Time.now.to_i.to_s, - callback_url: callback_url, + callback_url:, customer_url: return_url, amount: invoices_total&.format(symbol: nil, thousands_separator: false, decimal_mark: '.'), order_reference: SecureRandom.hex(15), transaction_type: 'charge', locale: language, - hmac_fields: '', + hmac_fields: '' }.with_indifferent_access end end diff --git a/app/services/eis_billing/send_callback_service.rb b/app/services/eis_billing/send_callback_service.rb index da00cdeac..d8064d693 100644 --- a/app/services/eis_billing/send_callback_service.rb +++ b/app/services/eis_billing/send_callback_service.rb @@ -22,9 +22,6 @@ def send_it end def billing_callback_url - Rails.logger.info '------ callback ---- reference_number' - Rails.logger.info "------ callback ---- reference_number: #{reference_number}" - Rails.logger.info '------ callback ---- reference_number' "/api/v1/callback_handler/callback?payment_reference=#{reference_number}" end end From 1eaac0bc56e4302ecda250bf03abf617c9d94684 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Thu, 29 May 2025 14:24:52 +0300 Subject: [PATCH 27/45] added skip auth checker to profile in create action --- app/controllers/api/v1/profiles_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 25bfda760..2b920ab6b 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -4,6 +4,7 @@ class ProfilesController < BaseController respond_to :json skip_before_action :verify_authenticity_token + skip_before_action :check_for_authentication, only: %i[create] def update if current_user.update(params_for_update) From 145a272eedc0adcde9bb6cbecc9901ef8623fa6a Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 13 Jun 2025 13:02:32 +0300 Subject: [PATCH 28/45] added api healthcare --- Gemfile | 1 + Gemfile.lock | 1 + app/controllers/api/v1/api_healthcare_controlle.rb | 9 +++++++++ app/controllers/api/v1/base_controller.rb | 7 +++++++ app/models/feature.rb | 5 +++++ config/customization.yml.sample | 3 +++ config/routes.rb | 1 + 7 files changed, 27 insertions(+) create mode 100644 app/controllers/api/v1/api_healthcare_controlle.rb diff --git a/Gemfile b/Gemfile index d22564806..7d0c7222b 100644 --- a/Gemfile +++ b/Gemfile @@ -82,6 +82,7 @@ group :development do gem 'rubocop-shopify' gem 'ruby-lsp' # gem "rubocop-thread_safety" + gem 'foreman' gem "hotwire-spark" end diff --git a/Gemfile.lock b/Gemfile.lock index 660992e7e..22bd6a797 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -161,6 +161,7 @@ GEM docile (1.4.1) domain_name (0.6.20240107) drb (2.2.3) + erubi (1.13.1) dry-auto_inject (1.0.1) dry-core (~> 1.0) zeitwerk (~> 2.6) diff --git a/app/controllers/api/v1/api_healthcare_controlle.rb b/app/controllers/api/v1/api_healthcare_controlle.rb new file mode 100644 index 000000000..63428b844 --- /dev/null +++ b/app/controllers/api/v1/api_healthcare_controlle.rb @@ -0,0 +1,9 @@ +class Api::V1::ApiHealthcareController < ApplicationController + def index + if Feature.mobile_api_enabled? + render json: { message: 'API is healthy' } + else + render json: { message: 'API is turned off' }, status: 403 + end + end +end diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index bc7d5dd93..0db01be4d 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -1,6 +1,7 @@ module Api module V1 class BaseController < ApplicationController + before_action :api_turn_off before_action :check_for_authentication respond_to :json @@ -26,6 +27,12 @@ def check_for_authentication private + def api_turn_off + return if Feature.mobile_api_enabled? + + render json: { errors: 'API is turned off' }, status: 403 + end + def decode_token(token) JWT.decode(token, AuctionCenter::Application.config.customization[:jwt_secret], true, { algorithm: 'HS256' }) end diff --git a/app/models/feature.rb b/app/models/feature.rb index 5c4f9a5a8..8481586ba 100644 --- a/app/models/feature.rb +++ b/app/models/feature.rb @@ -15,4 +15,9 @@ def self.open_ai_integration_enabled? !!AuctionCenter::Application.config.customization[:openai] &.compact&.fetch(:enabled, false) end + + def self.mobile_api_enabled? + !!AuctionCenter::Application.config.customization[:mobile_api] + &.compact&.fetch(:enabled, false) + end end diff --git a/config/customization.yml.sample b/config/customization.yml.sample index 939f11f2f..9525ea7a3 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -25,6 +25,9 @@ default: &default mobile_secret_key: '' mobile_secret_word: '' + mobile_api: + enabled: false + mailer: # Host to which links from emails should redirect to host: 'https://auction.example.test' diff --git a/config/routes.rb b/config/routes.rb index 5fb0ab2f5..7a88ebca2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ namespace :api do namespace :v1 do + get 'healthcare', to: 'api_healthcare#index' resource :stream_name, only: :show resources :offers, only: [:create, :index] resource :autobiders, only: :create From ea788ba8b91de4db3e6909e5c87d344c245f4d6d Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 20 Jun 2025 10:38:37 +0300 Subject: [PATCH 29/45] added api healthcheck --- .../api/v1/api_healthcare_controlle.rb | 9 ----- .../api/v1/api_healthcare_controller.rb | 37 +++++++++++++++++++ app/models/feature.rb | 5 +++ config/customization.yml.sample | 1 + 4 files changed, 43 insertions(+), 9 deletions(-) delete mode 100644 app/controllers/api/v1/api_healthcare_controlle.rb create mode 100644 app/controllers/api/v1/api_healthcare_controller.rb diff --git a/app/controllers/api/v1/api_healthcare_controlle.rb b/app/controllers/api/v1/api_healthcare_controlle.rb deleted file mode 100644 index 63428b844..000000000 --- a/app/controllers/api/v1/api_healthcare_controlle.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Api::V1::ApiHealthcareController < ApplicationController - def index - if Feature.mobile_api_enabled? - render json: { message: 'API is healthy' } - else - render json: { message: 'API is turned off' }, status: 403 - end - end -end diff --git a/app/controllers/api/v1/api_healthcare_controller.rb b/app/controllers/api/v1/api_healthcare_controller.rb new file mode 100644 index 000000000..fd618dabe --- /dev/null +++ b/app/controllers/api/v1/api_healthcare_controller.rb @@ -0,0 +1,37 @@ +class Api::V1::ApiHealthcareController < ApplicationController + respond_to :json + + def index + return render json: { message: 'API is turned off' }, status: 403 unless Feature.mobile_api_enabled? + + app_version = request.headers['App-Version'] + min_version = Feature.minimum_mobile_version + + if app_version.blank? + return render json: { + message: 'App version required', + required_update: true + }, status: 426 # Upgrade Required + end + + if version_outdated?(app_version, min_version) + return render json: { + message: 'App update required', + required_update: true, + min_version:, + current_version: app_version + }, status: 426 # Upgrade Required + end + + render json: { + message: 'API is healthy', + current_version: app_version + } + end + + private + + def version_outdated?(current, minimum) + Gem::Version.new(current) < Gem::Version.new(minimum) + end +end diff --git a/app/models/feature.rb b/app/models/feature.rb index 8481586ba..b8968ee17 100644 --- a/app/models/feature.rb +++ b/app/models/feature.rb @@ -20,4 +20,9 @@ def self.mobile_api_enabled? !!AuctionCenter::Application.config.customization[:mobile_api] &.compact&.fetch(:enabled, false) end + + def self.minimum_mobile_version + AuctionCenter::Application.config.customization[:mobile_api] + &.compact&.fetch(:minimum_version, '1.0.0') + end end diff --git a/config/customization.yml.sample b/config/customization.yml.sample index 9525ea7a3..8e489be95 100644 --- a/config/customization.yml.sample +++ b/config/customization.yml.sample @@ -27,6 +27,7 @@ default: &default mobile_api: enabled: false + minimum_version: '1.0.0' mailer: # Host to which links from emails should redirect to From ef0dd22447f253542e34140dafac6eb55feb8c99 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 21 Jul 2025 13:05:39 +0300 Subject: [PATCH 30/45] fixes --- app/controllers/api/v1/base_controller.rb | 8 ++++++++ app/controllers/api/v1/profiles_controller.rb | 17 +++++++++++++---- config/locales/en.yml | 2 +- config/locales/et.yml | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 0db01be4d..e1d6c177b 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -5,6 +5,8 @@ class BaseController < ApplicationController before_action :check_for_authentication respond_to :json + before_action :set_locale + def check_for_authentication if token = extract_token_from_header begin @@ -43,6 +45,12 @@ def extract_token_from_header header.split(' ').last if header.start_with?('Bearer ') end + + def set_locale + return unless user_signed_in? + + I18n.locale = current_user.locale || I18n.default_locale + end end end end diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 2b920ab6b..8161b01fc 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -10,30 +10,39 @@ def update if current_user.update(params_for_update) render json: current_user, status: :ok else - Rails.logger.info @user.errors.inspect - render json: current_user.errors, status: :unprocessable_entity + Rails.logger.info current_user.errors.inspect + render json: { errors: user.errors.to_hash(true) }, status: :unprocessable_entity end end def create - user = User.new(params_for_create) + user = initialize_user(params_for_create) + set_locale(user) if user.save sign_in(User, user) render json: user, status: :created else Rails.logger.info user.errors.inspect - render json: user.errors, status: :unprocessable_entity + render json: { errors: user.errors.to_hash(true) }, status: :unprocessable_entity end end private + def initialize_user(user) + User.new(params_for_create) + end + def params_for_create params.require(:user).permit(:email, :password, :password_confirmation, :country_code, :given_names, :surname, :mobile_phone, :accepts_terms_and_conditions, :locale, :daily_summary, :identity_code) end + + def set_locale(user) + I18n.locale = user.locale || I18n.default_locale + end def params_for_update params.require(:user).permit(:email, :country_code, :given_names, :surname, :mobile_phone) diff --git a/config/locales/en.yml b/config/locales/en.yml index 613fa6a9b..7ec242b20 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -111,7 +111,7 @@ en: created_at: "User created at" updated_at: "Updated at" - is_invalid: "%{attribute} is invalid" + is_invalid: "is invalid" invalid: "invalid" value_is_not_safe: | Please note that the only characters allowed are UTF-characters of english/estonian alphabet, diff --git a/config/locales/et.yml b/config/locales/et.yml index 5ef1e4881..aad45d877 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -88,7 +88,7 @@ et: created_at: "Kasutaja loodud" updated_at: "Muudetud" - is_invalid: "vale" + is_invalid: "on vigane" invalid: "vale" value_is_not_safe: | Nime ja aadressi andmetes on lubatud kasutada vaid inglise ja eesti tähestiku tähti, numbreid, From b73362a0ae22329225606d02dceefa7793132828 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 22 Jul 2025 14:15:39 +0300 Subject: [PATCH 31/45] fixed profile controller --- app/controllers/api/v1/profiles_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 8161b01fc..497d175c8 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -17,7 +17,7 @@ def update def create user = initialize_user(params_for_create) - set_locale(user) + set_locale_for user if user.save sign_in(User, user) @@ -30,7 +30,7 @@ def create private - def initialize_user(user) + def initialize_user(params_for_create) User.new(params_for_create) end @@ -40,7 +40,7 @@ def params_for_create :locale, :daily_summary, :identity_code) end - def set_locale(user) + def set_locale_for(user) I18n.locale = user.locale || I18n.default_locale end From 99497779e2c181295f2b95e9d62461f3d79faeed Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 23 Jul 2025 16:31:48 +0300 Subject: [PATCH 32/45] added tests for autobider and auction --- .gitignore | 2 +- Gemfile.lock | 1 - .../api/v1/autobiders_controller.rb | 49 ++- app/models/autobider.rb | 14 + config/locales/autobider.en.yml | 4 + config/locales/autobider.et.yml | 4 + .../api/auctions_controller_test.rb | 30 ++ .../api/v1/autobiders_controller_test.rb | 396 ++++++++++++++++++ 8 files changed, 485 insertions(+), 15 deletions(-) create mode 100644 config/locales/autobider.en.yml create mode 100644 config/locales/autobider.et.yml create mode 100644 test/controllers/api/auctions_controller_test.rb create mode 100644 test/controllers/api/v1/autobiders_controller_test.rb diff --git a/.gitignore b/.gitignore index 26bd403d3..684ac4cb7 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,4 @@ CLAUDE.md /app/assets/builds/* !/app/assets/builds/.keep .cursorindexingignore -.specstory \ No newline at end of file +.specstory diff --git a/Gemfile.lock b/Gemfile.lock index 22bd6a797..660992e7e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -161,7 +161,6 @@ GEM docile (1.4.1) domain_name (0.6.20240107) drb (2.2.3) - erubi (1.13.1) dry-auto_inject (1.0.1) dry-core (~> 1.0) zeitwerk (~> 2.6) diff --git a/app/controllers/api/v1/autobiders_controller.rb b/app/controllers/api/v1/autobiders_controller.rb index ccbbd0340..7ba99c4e5 100644 --- a/app/controllers/api/v1/autobiders_controller.rb +++ b/app/controllers/api/v1/autobiders_controller.rb @@ -2,33 +2,50 @@ module Api module V1 class AutobidersController < BaseController respond_to :json - skip_before_action :verify_authenticity_token + before_action :price_must_be_positive, only: :create def create @autobider = Autobider.find_by(user_id: current_user.id, id: strong_params[:id]) + initialize_or_assign_values_for_autobider - if @autobider.nil? - @autobider = Autobider.new(strong_params.merge(user: current_user, enable: true)) + if @autobider.save + process_autobid + render json: { status: 'ok' }, status: :ok else - @autobider.price = strong_params[:price] - @autobider.enable = true + render json: { status: 'error', message: @autobider.errors.full_messages.join(', ') }, status: :unprocessable_entity end + rescue StandardError => e + Rails.logger.error "Error: #{e.message}" + render json: { status: 'error', message: e.message }, status: :unprocessable_entity + end - if @autobider.save - auction = Auction.where(domain_name: @autobider.domain_name).order(:created_at).last - AutobiderService.autobid(auction) unless skip_autobid(auction) + private - Auctions::UpdateListBroadcastService.call({ auction: }) + def process_autobid + auction = Auction.where(domain_name: @autobider.domain_name).order(:created_at).last + autobid!(auction) + call_broadcast_service(auction) + end - render json: { status: 'ok' }, status: :ok + def autobid!(auction) + return if skip_autobid(auction) + + AutobiderService.autobid(auction) + end + + def call_broadcast_service(auction) + Auctions::UpdateListBroadcastService.call({ auction: }) + end + + def initialize_or_assign_values_for_autobider + if @autobider.nil? + @autobider = Autobider.new(strong_params.except(:id).merge(user: current_user, enable: true)) else - render json: { status: 'error' }, status: :unprocessable_entity + @autobider.assign_initialize_params_for_mobile_api(strong_params) end end - private - def skip_autobid(auction) return false if auction.offers.empty? @@ -39,6 +56,12 @@ def skip_autobid(auction) def strong_params params.require(:autobider).permit(:id, :domain_name, :price) end + + def price_must_be_positive + return if strong_params[:price].to_d > 0 + + render json: { status: 'error', message: 'Price must be greater than 0' }, status: :unprocessable_entity + end end end end diff --git a/app/models/autobider.rb b/app/models/autobider.rb index 5dc35e77f..132d824ff 100644 --- a/app/models/autobider.rb +++ b/app/models/autobider.rb @@ -2,7 +2,14 @@ class Autobider < ApplicationRecord belongs_to :user validates :cents, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true + validates :domain_name, presence: true validates :domain_name, uniqueness: { scope: :user_id } + validate :validate_domain_name_for_exists + + def assign_initialize_params_for_mobile_api(strong_params) + self.price = strong_params[:price] + self.enable = true + end def price Money.new(cents, Setting.find_by(code: 'auction_currency').retrieve) @@ -12,4 +19,11 @@ def price=(value) price = Money.from_amount(value.to_d, Setting.find_by(code: 'auction_currency').retrieve) self.cents = price.cents.positive? ? price.cents : nil end + + def validate_domain_name_for_exists + domain = Auction.where(domain_name: domain_name).order(:created_at).last + return if domain.present? + + errors.add(:domain_name, I18n.t('autobider.domain_name.does_not_exist')) + end end diff --git a/config/locales/autobider.en.yml b/config/locales/autobider.en.yml new file mode 100644 index 000000000..426e77bb2 --- /dev/null +++ b/config/locales/autobider.en.yml @@ -0,0 +1,4 @@ +en: + autobider: + domain_name: + does_not_exist: "Domain name does not exist" \ No newline at end of file diff --git a/config/locales/autobider.et.yml b/config/locales/autobider.et.yml new file mode 100644 index 000000000..6f57d2aba --- /dev/null +++ b/config/locales/autobider.et.yml @@ -0,0 +1,4 @@ +et: + autobider: + domain_name: + does_not_exist: "Domaeni nimi ei ole olemas" diff --git a/test/controllers/api/auctions_controller_test.rb b/test/controllers/api/auctions_controller_test.rb new file mode 100644 index 000000000..fff5bd425 --- /dev/null +++ b/test/controllers/api/auctions_controller_test.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::AuctionsControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @auction = auctions(:valid_with_offers) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + end + + test 'should show auction data for authorized user' do + get api_v1_auctions_url(auction_id: @auction.uuid), + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :success + json = JSON.parse(response.body) + assert json['offer'] + assert json['autobider'] + assert json['billing_profiles'] + end + + test 'should return unauthorized without token' do + get api_v1_auctions_url(auction_id: @auction.uuid), as: :json + assert_response :unauthorized + end +end \ No newline at end of file diff --git a/test/controllers/api/v1/autobiders_controller_test.rb b/test/controllers/api/v1/autobiders_controller_test.rb new file mode 100644 index 000000000..c2274cf71 --- /dev/null +++ b/test/controllers/api/v1/autobiders_controller_test.rb @@ -0,0 +1,396 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::AutobidersControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @second_user = users(:second_place_participant) + @english_auction = auctions(:english) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + @second_user_token = JWT.encode({ sub: @second_user.id }, @jwt_secret, 'HS256') + + # Enable mobile API for tests + Rails.application.config.customization[:mobile_api] = { enabled: true } + + @active_auction = Auction.new( + domain_name: 'active-test.test', + starts_at: Time.now.utc - 1.hour, + ends_at: Time.now.utc + 1.hour, + platform: 'english', + starting_price: 5.0, + min_bids_step: 5.0, + slipping_end: 5, + skip_validation: true + ) + @active_auction.save! + end + + test 'should return 403 when mobile API is disabled' do + Rails.application.config.customization[:mobile_api] = { enabled: false } + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @english_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :forbidden + json = JSON.parse(response.body) + assert_equal 'API is turned off', json['errors'] + end + + test 'should return unauthorized without token' do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @english_auction.domain_name, price: 100.0 } }, + as: :json + + assert_response :unauthorized + json = JSON.parse(response.body) + assert_equal 'Unauthorized', json['errors'] + end + + test 'should return unauthorized with invalid token' do + invalid_token = JWT.encode({ sub: @user.id }, 'invalid_secret', 'HS256') + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @english_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{invalid_token}" }, + as: :json + + assert_response :unauthorized + end + + test 'should return unauthorized with malformed token' do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @english_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer invalid_token_format" }, + as: :json + + assert_response :unauthorized + end + + test 'should create new autobider successfully' do + assert_difference('Autobider.count', 1) do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :ok + json = JSON.parse(response.body) + assert_equal 'ok', json['status'] + + autobider = Autobider.last + assert_equal @user.id, autobider.user_id + assert_equal @active_auction.domain_name, autobider.domain_name + assert_equal 10000, autobider.cents # 100.0 EUR in cents + assert autobider.enable + end + + test 'should update existing autobider' do + existing_autobider = Autobider.create!( + user: @user, + domain_name: @active_auction.domain_name, + cents: 5000, + enable: false + ) + + assert_no_difference('Autobider.count') do + post api_v1_autobiders_url, + params: { + autobider: { + id: existing_autobider.id, + domain_name: @active_auction.domain_name, + price: 150.0 + } + }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :ok + json = JSON.parse(response.body) + assert_equal 'ok', json['status'] + + existing_autobider.reload + assert_equal 15000, existing_autobider.cents # 150.0 EUR in cents + assert existing_autobider.enable + end + + test 'should not update autobider belonging to different user' do + other_autobider = Autobider.create!( + user: @second_user, + domain_name: @active_auction.domain_name, + cents: 5000, + enable: false + ) + + assert_difference('Autobider.count', 1) do + post api_v1_autobiders_url, + params: { + autobider: { + id: other_autobider.id, + domain_name: @active_auction.domain_name, + price: 150.0 + } + }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :ok + other_autobider.reload + assert_equal 5000, other_autobider.cents # Unchanged + assert_not other_autobider.enable # Unchanged + end + + test 'should return error for invalid autobider data' do + assert_no_difference('Autobider.count') do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: -10.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :unprocessable_entity + json = JSON.parse(response.body) + assert_equal 'error', json['status'] + end + + test 'should return error for zero price' do + assert_no_difference('Autobider.count') do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :unprocessable_entity + json = JSON.parse(response.body) + assert_equal 'error', json['status'] + end + + test 'should call AutobiderService.autobid when creating autobider' do + spy_on_autobider_service = Spy.on(AutobiderService, :autobid) + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + assert spy_on_autobider_service.has_been_called? + end + + test 'should skip autobid when user is last bidder' do + # Create an offer from the same user first + Offer.create!( + auction: @active_auction, + user: @user, + cents: 5000, + billing_profile: billing_profiles(:private_person), + username: 'testuser' + ) + + spy_on_autobider_service = Spy.on(AutobiderService, :autobid) + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + assert_not spy_on_autobider_service.has_been_called? + end + + test 'should not skip autobid when different user is last bidder' do + second_user_profile = BillingProfile.create!( + user: @second_user, + name: 'Second User Profile', + alpha_two_country_code: 'EE', + city: 'Tallinn', + street: 'Test St', + postal_code: '12345' + ) + + Offer.create!( + auction: @active_auction, + user: @second_user, + cents: 5000, + billing_profile: second_user_profile, + username: 'otheruser' + ) + + spy_on_autobider_service = Spy.on(AutobiderService, :autobid) + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + assert spy_on_autobider_service.has_been_called? + end + + test 'should call broadcast service after successful creation' do + spy_on_broadcast_service = Spy.on(Auctions::UpdateListBroadcastService, :call) + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + assert spy_on_broadcast_service.has_been_called? + end + + test 'should not handle domain name that does not exist' do + assert_no_difference('Autobider.count') do + post api_v1_autobiders_url, + params: { autobider: { domain_name: 'nonexistent.test', price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :unprocessable_entity + json = JSON.parse(response.body) + assert_equal 'error', json['status'] + assert_equal I18n.t('autobider.domain_name.does_not_exist'), json['message'] + end + + test 'should handle missing required parameters' do + assert_no_difference('Autobider.count') do + post api_v1_autobiders_url, + params: { autobider: { price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :unprocessable_entity + json = JSON.parse(response.body) + assert_equal 'error', json['status'] + end + + test 'should handle empty parameters' do + post api_v1_autobiders_url, + params: {}, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :bad_request + end + + test 'should set autobider enabled to true for both new and existing' do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + autobider = Autobider.last + assert autobider.enable + + autobider.update!(enable: false) + + post api_v1_autobiders_url, + params: { + autobider: { + id: autobider.id, + domain_name: @active_auction.domain_name, + price: 200.0 + } + }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + autobider.reload + assert autobider.enable + end + + test 'should handle duplicate domain name for same user by updating' do + existing = Autobider.create!( + user: @user, + domain_name: @active_auction.domain_name, + cents: 5000, + enable: true + ) + + existed_autobider = Autobider.where(domain_name: @active_auction.domain_name).order(:created_at).last + + assert_no_difference('Autobider.count') do + post api_v1_autobiders_url, + params: { autobider: { id: existed_autobider.id, domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + end + + assert_response :ok + json = JSON.parse(response.body) + assert_equal 'ok', json['status'] + + existing.reload + assert_equal 10000, existing.cents + end + + test 'should find auction by domain name and call autobid service' do + spy_on_autobider_service = Spy.on(AutobiderService, :autobid) + spy_on_broadcast_service = Spy.on(Auctions::UpdateListBroadcastService, :call) + + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 100.0 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + assert spy_on_autobider_service.has_been_called? + assert spy_on_broadcast_service.has_been_called? + end + + test 'should handle decimal prices correctly' do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: 123.45 } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + + autobider = Autobider.last + assert_equal 12345, autobider.cents + end + + test 'should handle string prices correctly' do + post api_v1_autobiders_url, + params: { autobider: { domain_name: @active_auction.domain_name, price: '99.99' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + + autobider = Autobider.last + assert_equal 9999, autobider.cents + end + + test 'should only accept permitted parameters' do + post api_v1_autobiders_url, + params: { + autobider: { + domain_name: @active_auction.domain_name, + price: 100.0, + enable: false, + user_id: @second_user.id, + extra_param: 'ignored' + } + }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :ok + + autobider = Autobider.last + assert_equal @user.id, autobider.user_id + assert autobider.enable + end + +end \ No newline at end of file From 3c79b92ed2693ef8330a8cd4c0c7b1bf9aded490 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 28 Jul 2025 14:28:23 +0300 Subject: [PATCH 33/45] cover api by tests --- app/controllers/api/v1/offers_controller.rb | 70 ++++++++++++------- app/controllers/api/v1/profiles_controller.rb | 2 +- .../api/{ => v1}/auctions_controller_test.rb | 0 .../v1/billing_profiles_controller_test.rb | 53 ++++++++++++++ .../api/v1/invoices_controller_test.rb | 38 ++++++++++ .../api/v1/offers_controller_test.rb | 56 +++++++++++++++ .../api/v1/passwords_controller_test.rb | 41 +++++++++++ .../api/v1/profiles_controller_test.rb | 39 +++++++++++ 8 files changed, 271 insertions(+), 28 deletions(-) rename test/controllers/api/{ => v1}/auctions_controller_test.rb (100%) create mode 100644 test/controllers/api/v1/billing_profiles_controller_test.rb create mode 100644 test/controllers/api/v1/invoices_controller_test.rb create mode 100644 test/controllers/api/v1/offers_controller_test.rb create mode 100644 test/controllers/api/v1/passwords_controller_test.rb create mode 100644 test/controllers/api/v1/profiles_controller_test.rb diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index f45bfa292..fc0ff799b 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -4,6 +4,7 @@ class OffersController < BaseController respond_to :json skip_before_action :verify_authenticity_token + before_action :set_auction, only: [:create] def index offers = Offer.includes(:auction) @@ -18,47 +19,62 @@ def index ) end - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength def create - auction = Auction.find_by(uuid: params[:bid][:auction_id]) - return if auction.nil? + @offer = initialize_or_assign_price_to_offer - billing_profile = current_user.billing_profiles.find_by(id: params[:bid][:billing_profile_id]) + if @offer.save + process_english_auction if @auction.english? + render json: { status: 'ok' }, status: :ok + else + Rails.logger.info "Offer errors (details): #{ @offer.errors.details }" + render json: { status: 'error', errors: @offer.errors.full_messages }, status: :unprocessable_entity + end + end + + private + + def set_auction + @auction = Auction.find_by(uuid: offer_params[:auction_id]) + return if @auction.present? + + render json: { status: 'not_found', errors: "Auction with #{offer_params[:auction_id]} uuid not found" }, status: :not_found + end + + def process_english_auction + call_broadcast + @auction.update_minimum_bid_step(offer_params[:price].to_f) + AutobiderService.autobid(@auction) + @auction.update_ends_at(@offer) + end - offer = auction.offer_from_user(current_user.id) + def call_broadcast + Auctions::UpdateListBroadcastService.call({ auction: @auction }) + end + + def initialize_or_assign_price_to_offer + offer = @auction.offer_from_user(current_user.id) if offer.nil? offer = Offer.new( - auction:, + auction: @auction, user: current_user, - cents: Money.from_amount(params[:bid][:price]).cents, + cents: Money.from_amount(offer_params[:price]).cents, billing_profile:, - username: auction.english? ? Username::GenerateUsernameService.new.call : nil + username: @auction.english? ? Username::GenerateUsernameService.new.call : nil ) else - offer.cents = Money.from_amount(params[:bid][:price]).cents + offer.cents = Money.from_amount(offer_params[:price]).cents end - if offer.save - if auction.english? - Auctions::UpdateListBroadcastService.call({ auction: }) - - auction.update_minimum_bid_step(params[:bid][:price].to_f) - - AutobiderService.autobid(auction) - auction.update_ends_at(offer) - end - - render json: { status: 'ok' }, status: :ok - else + offer + end - Rails.logger.info('----') - Rails.logger.info(offer.errors.inspect) - Rails.logger.info('----') + def billing_profile + current_user.billing_profiles.find_by(id: offer_params[:billing_profile_id]) + end - render json: { status: 'error' }, status: :unprocessable_entity - end + def offer_params + params.require(:bid).permit(:auction_id, :price, :billing_profile_id) end end end diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 497d175c8..7cbea3df1 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -20,7 +20,7 @@ def create set_locale_for user if user.save - sign_in(User, user) + # sign_in(User, user) render json: user, status: :created else Rails.logger.info user.errors.inspect diff --git a/test/controllers/api/auctions_controller_test.rb b/test/controllers/api/v1/auctions_controller_test.rb similarity index 100% rename from test/controllers/api/auctions_controller_test.rb rename to test/controllers/api/v1/auctions_controller_test.rb diff --git a/test/controllers/api/v1/billing_profiles_controller_test.rb b/test/controllers/api/v1/billing_profiles_controller_test.rb new file mode 100644 index 000000000..e27646a44 --- /dev/null +++ b/test/controllers/api/v1/billing_profiles_controller_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::BillingProfilesControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + end + + test 'should create billing profile' do + post api_v1_billing_profiles_url, + params: { billing_profile: { name: 'Test Billing Profile', vat_code: '1234567890', street: '123 Main St', city: 'Anytown', state: 'CA', postal_code: '12345', alpha_two_country_code: 'US', uuid: '123e4567-e89b-12d3-a456-426614174000' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :success + json = JSON.parse(response.body) + assert_equal 'Test Billing Profile', json['billing_profile']['name'] + assert_equal '1234567890', json['billing_profile']['vat_code'] + assert_equal '123 Main St', json['billing_profile']['street'] + assert_equal 'Anytown', json['billing_profile']['city'] + assert_equal 'CA', json['billing_profile']['state'] + assert_equal '12345', json['billing_profile']['postal_code'] + assert_equal 'US', json['billing_profile']['alpha_two_country_code'] + assert_equal '123e4567-e89b-12d3-a456-426614174000', json['billing_profile']['uuid'] + end + + test 'should update billing profile' do + put api_v1_billing_profile_url(@user.billing_profiles.first.id), + params: { billing_profile: { name: 'Updated Billing Profile' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :success + json = JSON.parse(response.body) + assert_equal 'Updated Billing Profile', json['billing_profile']['name'] + end + + test 'should not create billing profile with duplicate VAT code' do + vat_code = @user.billing_profiles.first.vat_code + post api_v1_billing_profiles_url, + params: { billing_profile: { name: 'Test Billing Profile', vat_code:, street: '123 Main St', city: 'Anytown', state: 'CA', postal_code: '12345', alpha_two_country_code: 'US', uuid: '123e4567-e89b-12d3-a456-426614174000' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :unprocessable_entity + json = JSON.parse(response.body) + assert_equal 'Vat code is already taken', json['errors']['vat_code'][0] + end +end \ No newline at end of file diff --git a/test/controllers/api/v1/invoices_controller_test.rb b/test/controllers/api/v1/invoices_controller_test.rb new file mode 100644 index 000000000..98132fe8e --- /dev/null +++ b/test/controllers/api/v1/invoices_controller_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::InvoicesControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + end + + test 'should get invoices' do + get api_v1_invoices_url, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + all_invoices = Invoice.all + + assert_response :success + json = JSON.parse(response.body) + assert_equal all_invoices.count, json['issued_invoices'].count + assert_equal all_invoices.count, json['paid_invoices'].count + assert_equal all_invoices.count, json['cancelled_payable_invoices'].count + assert_equal all_invoices.count, json['cancelled_expired_invoices'].count + assert_equal all_invoices.count, json['deposit_paid'].count + end + + test 'should get one off payment' do + get api_v1_invoices_one_off_payment_url(id: @user.invoices.first.id), + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :success + json = JSON.parse(response.body) + assert_equal 'https://example.com', json['oneoff_redirect_link'] + end +end \ No newline at end of file diff --git a/test/controllers/api/v1/offers_controller_test.rb b/test/controllers/api/v1/offers_controller_test.rb new file mode 100644 index 000000000..aa5b10de9 --- /dev/null +++ b/test/controllers/api/v1/offers_controller_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::OffersControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + travel_to Time.parse('2010-07-05 10:30 +0000').in_time_zone + end + + def test_show_offers + offers = Offer.includes(:auction) + .includes(:result) + .where(user_id: @user) + .order('auctions.ends_at DESC') + offers = offers.as_json( + include: %i[auction billing_profile], + methods: %i[auction_status api_price api_total api_bidders] + ) + + get api_v1_offers_url, headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, as: :json + assert_response :success + assert_equal offers, JSON.parse(response.body) + end + + def test_create_offer + auction = auctions(:valid_with_offers) + + assert_equal 50.00, auction.highest_price.to_f + post api_v1_offers_url, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + params: { bid: { auction_id: auction.uuid, price: 60 } }, + as: :json + + assert_response :success + assert_equal 60.00, auction.highest_price.to_f + end + + def test_update_offer_for_english_auction + auction = auctions(:english) + offer = auction.offers.first + post api_v1_offers_url, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + params: { bid: { auction_id: auction.uuid, price: 60, billing_profile_id: @user.billing_profiles.first.id } }, + as: :json + + auction.reload + + assert_response :success + assert_equal 60.00, auction.highest_price.to_f + assert_equal 61.00, auction.min_bids_step.to_f + end +end \ No newline at end of file diff --git a/test/controllers/api/v1/passwords_controller_test.rb b/test/controllers/api/v1/passwords_controller_test.rb new file mode 100644 index 000000000..259a2c7bf --- /dev/null +++ b/test/controllers/api/v1/passwords_controller_test.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::PasswordsControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + travel_to Time.parse('2010-07-05 10:30 +0000').in_time_zone + end + + def test_update_password + assert @user.valid_password?('password123') + + put api_v1_profiles_passwords_url, + params: { user: { password: 'newpassword', password_confirmation: 'newpassword', current_password: 'password123' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + @user.reload + + assert_response :success + assert @user.valid_password?('newpassword') + end + + def test_update_password_with_invalid_current_password + assert @user.valid_password?('password123') + + put api_v1_profiles_passwords_url, + params: { user: { password: 'newpassword', password_confirmation: 'newpassword', current_password: 'invalid' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + @user.reload + + assert_response :unprocessable_entity + assert @user.valid_password?('password123') + end +end \ No newline at end of file diff --git a/test/controllers/api/v1/profiles_controller_test.rb b/test/controllers/api/v1/profiles_controller_test.rb new file mode 100644 index 000000000..a559584af --- /dev/null +++ b/test/controllers/api/v1/profiles_controller_test.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'jwt' + +class Api::V1::ProfilesControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:participant) + @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' + @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + travel_to Time.parse('2010-07-05 10:30 +0000').in_time_zone + end + + def test_create_new_user_profile + assert_difference 'User.count' do + post api_v1_profiles_url, + params: { user: { email: 'test@example.com', password: 'password', password_confirmation: 'password', country_code: 'EE', given_names: 'John', surname: 'Doe', mobile_phone: '+1234567890', accepts_terms_and_conditions: true, locale: 'en' } }, + as: :json + + assert_response :success + end + end + + def test_update_user_profile + assert_equal @user.given_names, 'Joe John' + assert_equal @user.surname, 'Participant' + + put api_v1_profiles_url, + params: { user: { email: 'test@example.com', password: 'password', password_confirmation: 'password', country_code: 'EE', given_names: 'John', surname: 'Doe', mobile_phone: '+1234567890', accepts_terms_and_conditions: true, locale: 'en' } }, + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + @user.reload + + assert_response :success + assert_equal @user.given_names, 'John' + assert_equal @user.surname, 'Doe' + end +end From 07c06f217e9ca5787dd648ae6b59c5145040cc34 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 28 Jul 2025 16:39:44 +0300 Subject: [PATCH 34/45] xied invoices test --- app/controllers/api/v1/invoices_controller.rb | 9 +++++++-- test/controllers/api/v1/invoices_controller_test.rb | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index b3d7f2b00..f3830233c 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -5,6 +5,7 @@ class InvoicesController < BaseController skip_before_action :verify_authenticity_token before_action :set_auction, only: [:pay_deposit] + before_action :set_invoice, only: [:one_off_payment] # rubocop:disable Metrics/AbcSize def index @@ -33,11 +34,11 @@ def index end def one_off_payment - render json: { errors: 'Auction not found' } and return unless @auction + render json: { errors: 'Invoice not found' } and return unless @invoice response = EisBilling::OneoffService.call(invoice_number: @invoice.number.to_s, customer_url: mobile_payments_deposit_callback_url, - amount: params[:amount]) + amount: @invoice.amount) if response.result? render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } @@ -62,6 +63,10 @@ def pay_deposit private + def set_invoice + @invoice = Invoice.find_by(uuid: params[:id]) + end + def set_auction @auction = Auction.find_by(uuid: params[:id]) end diff --git a/test/controllers/api/v1/invoices_controller_test.rb b/test/controllers/api/v1/invoices_controller_test.rb index 98132fe8e..4813ec0c0 100644 --- a/test/controllers/api/v1/invoices_controller_test.rb +++ b/test/controllers/api/v1/invoices_controller_test.rb @@ -27,7 +27,7 @@ class Api::V1::InvoicesControllerTest < ActionDispatch::IntegrationTest end test 'should get one off payment' do - get api_v1_invoices_one_off_payment_url(id: @user.invoices.first.id), + get api_v1_one_off_payment_path(id: @user.invoices.first.uuid), headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, as: :json From b76c6e75d31d7c4beff8852bf1e0de44dcd52a4f Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 29 Jul 2025 15:18:37 +0300 Subject: [PATCH 35/45] updated tests and refactored api --- .../api/v1/autobiders_controller.rb | 3 -- app/controllers/api/v1/base_controller.rb | 43 +++++++++++++----- .../api/v1/billing_profiles_controller.rb | 6 --- .../v1/invoices/oneoff_payments_controller.rb | 31 +++++++++++++ .../v1/invoices/pay_deposits_controller.rb | 34 ++++++++++++++ app/controllers/api/v1/invoices_controller.rb | 38 ---------------- app/controllers/api/v1/offers_controller.rb | 2 +- .../api/v1/tara_auth_sessions_controller.rb | 2 +- config/routes.rb | 7 ++- .../v1/billing_profiles_controller_test.rb | 6 +++ .../api/v1/invoices_controller_test.rb | 44 +++++++++++++++---- .../api/v1/profiles_controller_test.rb | 8 ++++ 12 files changed, 153 insertions(+), 71 deletions(-) create mode 100644 app/controllers/api/v1/invoices/oneoff_payments_controller.rb create mode 100644 app/controllers/api/v1/invoices/pay_deposits_controller.rb diff --git a/app/controllers/api/v1/autobiders_controller.rb b/app/controllers/api/v1/autobiders_controller.rb index 7ba99c4e5..6dcaedff4 100644 --- a/app/controllers/api/v1/autobiders_controller.rb +++ b/app/controllers/api/v1/autobiders_controller.rb @@ -15,9 +15,6 @@ def create else render json: { status: 'error', message: @autobider.errors.full_messages.join(', ') }, status: :unprocessable_entity end - rescue StandardError => e - Rails.logger.error "Error: #{e.message}" - render json: { status: 'error', message: e.message }, status: :unprocessable_entity end private diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index e1d6c177b..d5582d44f 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -7,20 +7,19 @@ class BaseController < ApplicationController before_action :set_locale + rescue_from StandardError, with: :handle_standard_error + rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found + rescue_from JWT::DecodeError, with: :handle_jwt_decode_error + rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing + def check_for_authentication if token = extract_token_from_header - begin - decoded_token = decode_token(token) - payload = decoded_token[0] - - if user_id = payload['sub'] - @current_user = User.find_by(id: user_id) - sign_in(@current_user) if @current_user - end - rescue JWT::DecodeError => e - Rails.logger.error "JWT decode error: #{e.message}" - rescue StandardError => e - Rails.logger.error "Authentication error: #{e.message}" + decoded_token = decode_token(token) + payload = decoded_token[0] + + if user_id = payload['sub'] + @current_user = User.find_by(id: user_id) + sign_in(@current_user) if @current_user end end @@ -51,6 +50,26 @@ def set_locale I18n.locale = current_user.locale || I18n.default_locale end + + def handle_standard_error(e) + Rails.logger.error "Error: #{e.message}" + render json: { errors: e.message }, status: :internal_server_error + end + + def handle_record_not_found(e) + Rails.logger.error "Record not found: #{e.message}" + render json: { errors: 'Record not found' }, status: :not_found + end + + def handle_jwt_decode_error(e) + Rails.logger.error "JWT decode error: #{e.message}" + render json: { errors: 'Invalid token' }, status: :unauthorized + end + + def handle_parameter_missing(e) + Rails.logger.info "param is missing or the value is empty - autobider: #{e}" + render json: { errors: 'param is missing or the value is empty' }, status: :bad_request + end end end end diff --git a/app/controllers/api/v1/billing_profiles_controller.rb b/app/controllers/api/v1/billing_profiles_controller.rb index 67b6ab5d0..9b125597a 100644 --- a/app/controllers/api/v1/billing_profiles_controller.rb +++ b/app/controllers/api/v1/billing_profiles_controller.rb @@ -5,8 +5,6 @@ class BillingProfilesController < BaseController skip_before_action :verify_authenticity_token - rescue_from ActiveRecord::RecordNotUnique, with: :handle_unique_violation - def index @billing_profiles = current_user.billing_profiles @@ -39,10 +37,6 @@ def billing_profile_params params.require(:billing_profile).permit(:name, :vat_code, :street, :city, :state, :postal_code, :alpha_two_country_code, :uuid) end - - def handle_unique_violation - render json: { errors: 'A billing profile with the same VAT code for this user already exists.' }, status: 422 - end end end end diff --git a/app/controllers/api/v1/invoices/oneoff_payments_controller.rb b/app/controllers/api/v1/invoices/oneoff_payments_controller.rb new file mode 100644 index 000000000..dfd3791a3 --- /dev/null +++ b/app/controllers/api/v1/invoices/oneoff_payments_controller.rb @@ -0,0 +1,31 @@ +module Api + module V1 + module Invoices + class OneoffPaymentsController < BaseController + respond_to :json + skip_before_action :verify_authenticity_token + before_action :set_invoice, only: [:create] + + def create + response = EisBilling::OneoffService.call(invoice_number: @invoice.number.to_s, + customer_url: mobile_payments_deposit_callback_url, + amount: @invoice.total.to_f) + + if response.result? + render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } + else + render json: { errors: response.errors } + end + end + + private + + def set_invoice + @invoice = Invoice.find_by(uuid: params[:id]) + + raise ActiveRecord::RecordNotFound unless @invoice + end + end + end + end +end diff --git a/app/controllers/api/v1/invoices/pay_deposits_controller.rb b/app/controllers/api/v1/invoices/pay_deposits_controller.rb new file mode 100644 index 000000000..effce7d67 --- /dev/null +++ b/app/controllers/api/v1/invoices/pay_deposits_controller.rb @@ -0,0 +1,34 @@ +module Api + module V1 + module Invoices + class PayDepositsController < BaseController + respond_to :json + + skip_before_action :verify_authenticity_token + before_action :set_auction, only: [:create] + + def create + response = EisBilling::PayDepositService.call(amount: @auction.deposit, + customer_url: mobile_payments_deposit_callback_url, + description:) + if response.result? + render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } + else + render json: { errors: response.errors } + end + end + + private + + def description + "auction_deposit #{@auction.domain_name}, user_uuid #{current_user.uuid}, " \ + "user_email #{current_user.email}" + end + + def set_auction + @auction = Auction.find_by(uuid: params[:id]) + end + end + end + end +end \ No newline at end of file diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index f3830233c..711427e85 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -4,8 +4,6 @@ class InvoicesController < BaseController respond_to :json skip_before_action :verify_authenticity_token - before_action :set_auction, only: [:pay_deposit] - before_action :set_invoice, only: [:one_off_payment] # rubocop:disable Metrics/AbcSize def index @@ -33,44 +31,8 @@ def index } end - def one_off_payment - render json: { errors: 'Invoice not found' } and return unless @invoice - - response = EisBilling::OneoffService.call(invoice_number: @invoice.number.to_s, - customer_url: mobile_payments_deposit_callback_url, - amount: @invoice.amount) - - if response.result? - render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } - else - render json: { errors: response.errors } - end - end - - def pay_deposit - render json: { errors: 'Auction not found' } and return unless @auction - - description = "auction_deposit #{@auction.domain_name}, user_uuid #{current_user.uuid}, " \ - "user_email #{current_user.email}" - response = EisBilling::PayDepositService.call(amount: @auction.deposit, - customer_url: mobile_payments_deposit_callback_url, description:) - if response.result? - render json: { oneoff_redirect_link: response.instance['oneoff_redirect_link'] } - else - render json: { errors: response.errors } - end - end - private - def set_invoice - @invoice = Invoice.find_by(uuid: params[:id]) - end - - def set_auction - @auction = Auction.find_by(uuid: params[:id]) - end - def invoices_list_by_status(status) Invoice.accessible_by(current_ability) .where(user_id: current_user.id) diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index fc0ff799b..476ca3db1 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -37,7 +37,7 @@ def set_auction @auction = Auction.find_by(uuid: offer_params[:auction_id]) return if @auction.present? - render json: { status: 'not_found', errors: "Auction with #{offer_params[:auction_id]} uuid not found" }, status: :not_found + render_error("Auction with #{offer_params[:auction_id]} uuid not found") end def process_english_auction diff --git a/app/controllers/api/v1/tara_auth_sessions_controller.rb b/app/controllers/api/v1/tara_auth_sessions_controller.rb index c3bf42daa..9307747d4 100644 --- a/app/controllers/api/v1/tara_auth_sessions_controller.rb +++ b/app/controllers/api/v1/tara_auth_sessions_controller.rb @@ -10,7 +10,7 @@ def create message = Rails.configuration.customization[:mobile_secret_word] unless valid_hmac?(received_hmac, message) - render json: { error: 'Invalid HMAC' }, status: :unauthorized + render_error('Invalid HMAC') return end diff --git a/config/routes.rb b/config/routes.rb index 7a88ebca2..cfe42fb5b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,8 +20,11 @@ end end resources :invoices, only: :index - post 'pay_deposit', to: 'invoices#pay_deposit' - post 'one_off_payment', to: 'invoices#one_off_payment' + + scope module: :invoices do + resource :pay_deposits, only: :create + resource :oneoff_payments, only: :create + end resources :billing_profiles, only: %i[index update create destroy] end diff --git a/test/controllers/api/v1/billing_profiles_controller_test.rb b/test/controllers/api/v1/billing_profiles_controller_test.rb index e27646a44..d5acd29a7 100644 --- a/test/controllers/api/v1/billing_profiles_controller_test.rb +++ b/test/controllers/api/v1/billing_profiles_controller_test.rb @@ -8,6 +8,7 @@ class Api::V1::BillingProfilesControllerTest < ActionDispatch::IntegrationTest @user = users(:participant) @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + stub_billing_api_calls end test 'should create billing profile' do @@ -50,4 +51,9 @@ class Api::V1::BillingProfilesControllerTest < ActionDispatch::IntegrationTest json = JSON.parse(response.body) assert_equal 'Vat code is already taken', json['errors']['vat_code'][0] end + + def stub_billing_api_calls + stub_request(:patch, 'http://eis_billing_system:3000/api/v1/invoice/update_invoice_data') + .to_return(status: 200, body: { invoice_number: '1234567890', transaction_amount: 100 }.to_json, headers: {}) + end end \ No newline at end of file diff --git a/test/controllers/api/v1/invoices_controller_test.rb b/test/controllers/api/v1/invoices_controller_test.rb index 4813ec0c0..1b750c0a7 100644 --- a/test/controllers/api/v1/invoices_controller_test.rb +++ b/test/controllers/api/v1/invoices_controller_test.rb @@ -6,8 +6,11 @@ class Api::V1::InvoicesControllerTest < ActionDispatch::IntegrationTest setup do @user = users(:participant) + @deposit_auction = auctions(:deposit_english) @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') + + stub_billing_api_calls end test 'should get invoices' do @@ -15,24 +18,49 @@ class Api::V1::InvoicesControllerTest < ActionDispatch::IntegrationTest headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, as: :json - all_invoices = Invoice.all + all_invoices = @user.invoices assert_response :success json = JSON.parse(response.body) - assert_equal all_invoices.count, json['issued_invoices'].count - assert_equal all_invoices.count, json['paid_invoices'].count - assert_equal all_invoices.count, json['cancelled_payable_invoices'].count - assert_equal all_invoices.count, json['cancelled_expired_invoices'].count - assert_equal all_invoices.count, json['deposit_paid'].count + + assert_equal all_invoices.issued.count, json['issued_invoices'].count + assert_equal all_invoices.paid.count, json['paid_invoices'].count + assert_equal all_invoices.cancelled.with_ban.count, json['cancelled_payable_invoices'].count + assert_equal all_invoices.cancelled.without_ban.count, json['cancelled_expired_invoices'].count + assert_equal @user.domain_participate_auctions.count, json['deposit_paid'].count end test 'should get one off payment' do - get api_v1_one_off_payment_path(id: @user.invoices.first.uuid), + post api_v1_oneoff_payments_url(id: @user.invoices.first.uuid), + headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, + as: :json + + assert_response :success + json = JSON.parse(response.body) + assert_equal 'http://oneoff.redirect', json['oneoff_redirect_link'] + end + + test 'should get pay deposit' do + post api_v1_pay_deposits_url(id: @deposit_auction.uuid), headers: { 'HTTP_AUTHORIZATION' => "Bearer #{@token}" }, as: :json assert_response :success json = JSON.parse(response.body) - assert_equal 'https://example.com', json['oneoff_redirect_link'] + assert_equal 'http://oneoff.redirect', json['oneoff_redirect_link'] + end + + private + + def stub_billing_api_calls + message = { + oneoff_redirect_link: 'http://oneoff.redirect', + } + + stub_request(:post, 'http://eis_billing_system:3000/api/v1/invoice_generator/oneoff') + .to_return(status: 200, body: message.to_json, headers: {}) + + stub_request(:post, 'http://eis_billing_system:3000/api/v1/invoice_generator/deposit_prepayment') + .to_return(status: 200, body: message.to_json, headers: {}) end end \ No newline at end of file diff --git a/test/controllers/api/v1/profiles_controller_test.rb b/test/controllers/api/v1/profiles_controller_test.rb index a559584af..c29743e18 100644 --- a/test/controllers/api/v1/profiles_controller_test.rb +++ b/test/controllers/api/v1/profiles_controller_test.rb @@ -9,6 +9,7 @@ class Api::V1::ProfilesControllerTest < ActionDispatch::IntegrationTest @jwt_secret = Rails.application.config.customization[:jwt_secret] || 'jwt_secret' @token = JWT.encode({ sub: @user.id }, @jwt_secret, 'HS256') travel_to Time.parse('2010-07-05 10:30 +0000').in_time_zone + stub_billing_api_calls end def test_create_new_user_profile @@ -36,4 +37,11 @@ def test_update_user_profile assert_equal @user.given_names, 'John' assert_equal @user.surname, 'Doe' end + + private + + def stub_billing_api_calls + stub_request(:post, 'http://eis_billing_system:3000/api/v1/invoice_generator/reference_number_generator') + .to_return(status: 200, body: { reference_number: '1234567890' }.to_json, headers: {}) + end end From 586bdecfb97a3a59c3174c4c37b12ba2c49d10d1 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 30 Jul 2025 13:56:29 +0300 Subject: [PATCH 36/45] updated sign up issues --- app/controllers/api/v1/profiles_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/profiles_controller.rb b/app/controllers/api/v1/profiles_controller.rb index 7cbea3df1..8d4d7e790 100644 --- a/app/controllers/api/v1/profiles_controller.rb +++ b/app/controllers/api/v1/profiles_controller.rb @@ -35,7 +35,7 @@ def initialize_user(params_for_create) end def params_for_create - params.require(:user).permit(:email, :password, :password_confirmation, :country_code, + params.require(:user).permit(:email, :password, :password_confirmation, :country_code, :alpha_two_country_code, :given_names, :surname, :mobile_phone, :accepts_terms_and_conditions, :locale, :daily_summary, :identity_code) end From b1003cf1f85316a044b4446f407d094d0d037dd8 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 19 Nov 2025 10:20:56 +0200 Subject: [PATCH 37/45] normalize platforms --- Gemfile.lock | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 660992e7e..51ea05d80 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -188,6 +188,16 @@ GEM faraday-net_http (3.4.1) net-http (>= 0.5.0) ffi (1.17.2) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86-linux-gnu) + ffi (1.17.2-x86-linux-musl) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) ffi-compiler (1.3.2) ffi (>= 1.15.5) rake @@ -200,6 +210,30 @@ GEM google-protobuf (4.33.0) bigdecimal rake (>= 13) + google-protobuf (4.33.0-aarch64-linux-gnu) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-aarch64-linux-musl) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-x86-linux-gnu) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-x86-linux-musl) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-x86_64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-x86_64-linux-gnu) + bigdecimal + rake (>= 13) + google-protobuf (4.33.0-x86_64-linux-musl) + bigdecimal + rake (>= 13) hashdiff (1.2.1) hashie (5.0.0) hkdf (0.3.0) @@ -308,6 +342,22 @@ GEM nokogiri (1.18.10) mini_portile2 (~> 2.8.2) racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) noticed (1.6.3) http (>= 4.0.0) rails (>= 5.2.0) @@ -341,6 +391,13 @@ GEM racc pdfkit (0.8.7.3) pg (1.6.2) + pg (1.6.2-aarch64-linux) + pg (1.6.2-aarch64-linux-musl) + pg (1.6.2-aarch64-mingw-ucrt) + pg (1.6.2-arm64-darwin) + pg (1.6.2-x86_64-darwin) + pg (1.6.2-x86_64-linux) + pg (1.6.2-x86_64-linux-musl) pg_search (2.3.7) activerecord (>= 6.1) activesupport (>= 6.1) @@ -481,6 +538,36 @@ GEM sass-embedded (1.93.2) google-protobuf (~> 4.31) rake (>= 13) + sass-embedded (1.93.2-aarch64-linux-android) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-aarch64-linux-gnu) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-aarch64-linux-musl) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-aarch64-mingw-ucrt) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-arm-linux-androideabi) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-arm-linux-gnueabihf) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-arm-linux-musleabihf) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-arm64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-riscv64-linux-android) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-riscv64-linux-gnu) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-riscv64-linux-musl) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-x86_64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-x86_64-linux-android) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-x86_64-linux-gnu) + google-protobuf (~> 4.31) + sass-embedded (1.93.2-x86_64-linux-musl) + google-protobuf (~> 4.31) scenic (1.9.0) activerecord (>= 4.0.0) railties (>= 4.0.0) @@ -568,7 +655,28 @@ GEM zeitwerk (2.7.3) PLATFORMS + aarch64-linux + aarch64-linux-android + aarch64-linux-gnu + aarch64-linux-musl + aarch64-mingw-ucrt + arm-linux-androideabi + arm-linux-gnu + arm-linux-gnueabihf + arm-linux-musl + arm-linux-musleabihf + arm64-darwin + riscv64-linux-android + riscv64-linux-gnu + riscv64-linux-musl ruby + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux + x86_64-linux-android + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES airbrake From 71df9c2d8000c3aaa380dac65dea4a7876f4fb86 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 19 Nov 2025 10:38:02 +0200 Subject: [PATCH 38/45] move bindex to the development-test env in gemfile --- Gemfile | 1 + Gemfile.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 7d0c7222b..0c4f714bd 100644 --- a/Gemfile +++ b/Gemfile @@ -57,6 +57,7 @@ group :development, :test do gem 'bundler-audit' gem 'byebug', platforms: %i[mri mingw x64_mingw] gem 'pry' + gem 'bindex' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 51ea05d80..735c3d7fe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -681,6 +681,7 @@ PLATFORMS DEPENDENCIES airbrake amazing_print + bindex bootsnap (>= 1.1.0) brakeman bundler-audit From 73376471676312e28e748f336cffadb41ed8c21e Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 19 Nov 2025 13:16:31 +0200 Subject: [PATCH 39/45] remove dublicate --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index 0c4f714bd..536af2273 100644 --- a/Gemfile +++ b/Gemfile @@ -83,7 +83,6 @@ group :development do gem 'rubocop-shopify' gem 'ruby-lsp' # gem "rubocop-thread_safety" - gem 'foreman' gem "hotwire-spark" end From d4ab9b9aa893b6530c38824aee707b7c811c472a Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 25 Nov 2025 16:03:03 +0200 Subject: [PATCH 40/45] create billing profile if it not exists when bid --- Dockerfile.production | 130 ++++++++++++++++++++ app/controllers/api/v1/offers_controller.rb | 9 +- bin/docker-entrypoint | 94 ++++++++++++++ 3 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 Dockerfile.production create mode 100644 bin/docker-entrypoint diff --git a/Dockerfile.production b/Dockerfile.production new file mode 100644 index 000000000..f10f906c1 --- /dev/null +++ b/Dockerfile.production @@ -0,0 +1,130 @@ +# syntax = docker/dockerfile:1 + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile +ARG RUBY_VERSION=3.4.1 +FROM registry.docker.com/library/ruby:$RUBY_VERSION-bullseye AS base + +# Rails app lives here +WORKDIR /opt/webapps/app + +# Set production environment +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" \ + LANG=et_EE.UTF-8 \ + RAILS_SERVE_STATIC_FILES="true" \ + RAILS_LOG_TO_STDOUT="true" + +# Throw-away build stage to reduce size of final image +FROM base AS build + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Install packages needed to build gems and node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y \ + build-essential \ + imagemagick \ + curl \ + wget \ + gnupg2 \ + git \ + apt-utils \ + libpq-dev \ + libvips \ + node-gyp \ + pkg-config \ + python-is-python3 \ + libxslt1-dev \ + libxml2-dev \ + wkhtmltopdf \ + locales \ + postgresql-client + +# Configure locales +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i -e 's/# et_EE.UTF-8 UTF-8/et_EE.UTF-8 UTF-8/' /etc/locale.gen && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=et_EE.UTF-8 + +# Install JavaScript dependencies +ARG NODE_VERSION=18.16.0 +ARG YARN_VERSION=1.22.19 +ENV PATH=/usr/local/node/bin:$PATH +RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ + npm install -g yarn@$YARN_VERSION && \ + npm install -g n && \ + n stable && \ + rm -rf /tmp/node-build-master + +# Install application gems +COPY Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile + +# Install node modules +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile --check-files + +# Copy application code +COPY . . + +# Create necessary directories for K8s +RUN mkdir -p /opt/webapps/app/tmp/pids /opt/webapps/app/tmp/k8s /opt/webapps/app/log + +# Make entrypoint script executable +RUN chmod +x /opt/webapps/app/bin/docker-entrypoint + +# Precompile bootsnap code for faster boot times +RUN bundle exec bootsnap precompile app/ lib/ + +# Precompile assets with a real random secret key +RUN SECRET_KEY_BASE=$(openssl rand -hex 64) bundle exec rails assets:precompile + +# Final stage for app image +FROM base + +# Install packages needed for deployment (minimal set for production) +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y \ + curl \ + libvips \ + postgresql-client \ + wkhtmltopdf \ + imagemagick \ + locales \ + libxslt1-dev \ + libxml2-dev && \ + sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i -e 's/# et_EE.UTF-8 UTF-8/et_EE.UTF-8 UTF-8/' /etc/locale.gen && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=et_EE.UTF-8 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Copy built artifacts: gems, application +COPY --from=build /usr/local/bundle /usr/local/bundle +COPY --from=build /opt/webapps/app /opt/webapps/app + +# K8s specific configurations +RUN mkdir -p /opt/webapps/app/tmp/pids /opt/webapps/app/tmp/k8s /opt/webapps/app/storage + +# Ensure entrypoint script is executable in final image +RUN chmod +x /opt/webapps/app/bin/docker-entrypoint + +# Run and own only the runtime files as a non-root user for security +RUN useradd rails --create-home --shell /bin/bash && \ + chown -R rails:rails /opt/webapps/app/log /opt/webapps/app/tmp /opt/webapps/app/storage +USER rails:rails + +# Create a health check endpoint file for Kubernetes probes +RUN touch /opt/webapps/app/tmp/k8s/ready + +# Entrypoint prepares the database. +ENTRYPOINT ["/opt/webapps/app/bin/docker-entrypoint"] + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3000 +CMD ["./bin/rails", "server"] \ No newline at end of file diff --git a/app/controllers/api/v1/offers_controller.rb b/app/controllers/api/v1/offers_controller.rb index 476ca3db1..84617e5a4 100644 --- a/app/controllers/api/v1/offers_controller.rb +++ b/app/controllers/api/v1/offers_controller.rb @@ -20,13 +20,14 @@ def index end def create + BillingProfile.create_default_for_user(current_user.id) @offer = initialize_or_assign_price_to_offer if @offer.save process_english_auction if @auction.english? render json: { status: 'ok' }, status: :ok else - Rails.logger.info "Offer errors (details): #{ @offer.errors.details }" + Rails.logger.info "Offer errors (details): #{@offer.errors.details}" render json: { status: 'error', errors: @offer.errors.full_messages }, status: :unprocessable_entity end end @@ -70,7 +71,11 @@ def initialize_or_assign_price_to_offer end def billing_profile - current_user.billing_profiles.find_by(id: offer_params[:billing_profile_id]) + if offer_params[:billing_profile_id].present? + current_user.billing_profiles.find_by(id: offer_params[:billing_profile_id]) + else + current_user.billing_profiles.first + end end def offer_params diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100644 index 000000000..9c27725ba --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,94 @@ +#!/bin/bash +set -e + +# Remove a potentially pre-existing server.pid for Rails +rm -f /opt/webapps/app/tmp/pids/server.pid + +# Ensure proper environment variables are set for Rails in production mode +if [ "$RAILS_ENV" == "production" ] || [ "$RAILS_ENV" == "staging" ]; then + # Check if the master key is provided + if [ -n "$RAILS_MASTER_KEY" ]; then + echo "RAILS_MASTER_KEY environment variable is set. Using Rails credentials system." + # If no master key, check for secret key base + elif [ -n "$SECRET_KEY_BASE" ]; then + echo "SECRET_KEY_BASE environment variable is set. Using direct secret key configuration." + else + echo "WARNING: Neither RAILS_MASTER_KEY nor SECRET_KEY_BASE environment variables are set." + echo "Generating a temporary SECRET_KEY_BASE. This is okay for testing but NOT recommended for real production use." + export SECRET_KEY_BASE=$(openssl rand -hex 64) + fi +fi + +# Check for database connection settings - using the APP_DBHOST variable from database.yml +if [[ -n "$DATABASE_URL" || -n "$APP_DBHOST" ]]; then + # Get database host from DATABASE_URL or APP_DBHOST + if [ -n "$DATABASE_URL" ]; then + # Extract host from DATABASE_URL + DB_HOST=$(echo $DATABASE_URL | awk -F[@//] '{print $4}' | cut -d':' -f1) + else + DB_HOST=$APP_DBHOST + fi + + # Get database user from APP_DBUSER or default to postgres + DB_USER=${APP_DBUSER:-postgres} + + # Wait for PostgreSQL to be available + echo "Checking PostgreSQL connection to $DB_HOST..." + until pg_isready -h $DB_HOST -p ${APP_DBPORT:-5432} -U $DB_USER 2>/dev/null; do + echo "Waiting for PostgreSQL to be available..." + sleep 2 + done + echo "PostgreSQL is available." + + # Set up database if needed (useful for initial deployments) + if [ "${DB_SETUP:-false}" = "true" ]; then + echo "Setting up database..." + bundle exec rails db:setup + fi + + # Run database migrations if needed + if [ "${AUTO_MIGRATE:-false}" = "true" ]; then + echo "Running database migrations..." + bundle exec rails db:migrate + fi +else + echo "Database connection settings not found. Skipping database checks." +fi + +# Check for Redis connection if Redis is used +if [ -n "$REDIS_URL" ]; then + echo "Redis URL is set to $REDIS_URL" + # Attempting Redis connection + if command -v redis-cli &> /dev/null; then + echo "Checking Redis connection..." + REDIS_HOST=$(echo $REDIS_URL | sed -E 's/^redis:\/\/(.*):([0-9]+)\/([0-9]+)$/\1/') + REDIS_PORT=$(echo $REDIS_URL | sed -E 's/^redis:\/\/(.*):([0-9]+)\/([0-9]+)$/\2/') + + if [ -n "$REDIS_HOST" ] && [ -n "$REDIS_PORT" ]; then + until redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; do + echo "Waiting for Redis to become available..." + sleep 2 + done + echo "Redis is available." + else + echo "Could not parse Redis host and port from REDIS_URL. Skipping Redis connection check." + fi + else + echo "redis-cli not available. Skipping Redis connection check." + fi +fi + +# Prepare assets if needed (precompilation should have been done during build) +if [ "${PRECOMPILE_ASSETS:-false}" = "true" ] && { [ ! -d /opt/webapps/app/public/assets ] || [ -z "$(ls -A /opt/webapps/app/public/assets)" ]; }; then + echo "Precompiling assets..." + bundle exec rails assets:precompile +fi + +# Create readiness indicator for Kubernetes probes +mkdir -p /opt/webapps/app/tmp/k8s +touch /opt/webapps/app/tmp/k8s/ready + +echo "Auction Center application is ready to serve requests!" + +# Execute the container's main process (what's set as CMD in the Dockerfile) +exec "$@" \ No newline at end of file From 4a70438e592049f49c7e7ac017ef66bd35c55b27 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Thu, 27 Nov 2025 12:01:27 +0200 Subject: [PATCH 41/45] added deposit value for auction response --- app/views/auctions/index.json.jbuilder | 7 ++++++ app/views/mobile_payments/index.html.erb | 27 +++++++++++++++++++++++- config/locales/mobile_payments.en.yml | 6 ++++++ config/locales/mobile_payments.et.yml | 6 ++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 config/locales/mobile_payments.en.yml create mode 100644 config/locales/mobile_payments.et.yml diff --git a/app/views/auctions/index.json.jbuilder b/app/views/auctions/index.json.jbuilder index f24824109..de1e94788 100644 --- a/app/views/auctions/index.json.jbuilder +++ b/app/views/auctions/index.json.jbuilder @@ -11,4 +11,11 @@ json.array! @auctions_list do |auction| json.auction_type auction&.platform json.enable_deposit auction.enable_deposit json.requirement_deposit_in_cents auction.requirement_deposit_in_cents + json.user_deposit_paid do + if current_user + auction.domain_participate_auctions.exists?(user_id: current_user.id, status: 'paid') + else + false + end + end end diff --git a/app/views/mobile_payments/index.html.erb b/app/views/mobile_payments/index.html.erb index da60662fa..0ea7c5ca3 100644 --- a/app/views/mobile_payments/index.html.erb +++ b/app/views/mobile_payments/index.html.erb @@ -1 +1,26 @@ -Payment was paid! you can close the window arrrggg!!! \ No newline at end of file +
+
+
+
+
+
+ + + +
+ +

<%= t('mobile_payments.payment_successful') %>

+

<%= t('mobile_payments.payment_processed') %>

+ + + + +
+
+
+
+
\ No newline at end of file diff --git a/config/locales/mobile_payments.en.yml b/config/locales/mobile_payments.en.yml new file mode 100644 index 000000000..128c31601 --- /dev/null +++ b/config/locales/mobile_payments.en.yml @@ -0,0 +1,6 @@ +en: + mobile_payments: + payment_successful: "Payment Successful" + payment_processed: "Your payment has been processed successfully." + close_window_instruction: "You can now close this window and return to the application." + close_window: "Close Window" diff --git a/config/locales/mobile_payments.et.yml b/config/locales/mobile_payments.et.yml new file mode 100644 index 000000000..605a69489 --- /dev/null +++ b/config/locales/mobile_payments.et.yml @@ -0,0 +1,6 @@ +et: + mobile_payments: + payment_successful: "Makse õnnestus" + payment_processed: "Teie makse on edukalt töödeldud." + close_window_instruction: "Võite nüüd selle akna sulgeda ja naasta rakendusse." + close_window: "Sulge aken" From 48f8c352801aee51b776f7f04966ea8504510b07 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Fri, 28 Nov 2025 15:06:44 +0200 Subject: [PATCH 42/45] added delete action to billing profiles controller --- app/controllers/api/v1/billing_profiles_controller.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/controllers/api/v1/billing_profiles_controller.rb b/app/controllers/api/v1/billing_profiles_controller.rb index 9b125597a..0dad1b887 100644 --- a/app/controllers/api/v1/billing_profiles_controller.rb +++ b/app/controllers/api/v1/billing_profiles_controller.rb @@ -31,6 +31,17 @@ def create end end + def destroy + @billing_profile = current_user.billing_profiles.find(params[:id]) + + if @billing_profile.deletable? + @billing_profile.destroy! + render json: { message: 'Billing profile deleted successfully' }, status: :ok + else + render json: { errors: @billing_profile.errors.full_messages }, status: :unprocessable_entity + end + end + private def billing_profile_params From 9cd831dd3d9ebfb9dda7ae050e96da47fdea9b2e Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 2 Dec 2025 12:42:35 +0200 Subject: [PATCH 43/45] updated mobile payment page --- app/views/mobile_payments/index.html.erb | 36 +++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/app/views/mobile_payments/index.html.erb b/app/views/mobile_payments/index.html.erb index 0ea7c5ca3..7c23e87d4 100644 --- a/app/views/mobile_payments/index.html.erb +++ b/app/views/mobile_payments/index.html.erb @@ -1,25 +1,23 @@ -
-
-
-
-
-
- - - -
+
+
+
+
+ + + +
-

<%= t('mobile_payments.payment_successful') %>

-

<%= t('mobile_payments.payment_processed') %>

+

<%= t('mobile_payments.payment_successful') %>

+

<%= t('mobile_payments.payment_processed') %>

- +
+ <%= t('mobile_payments.close_window_instruction') %> +
- -
+
+
From f28ce843eaa1d304aa8ab3362463319ba1ea15ed Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 2 Dec 2025 13:35:15 +0200 Subject: [PATCH 44/45] added domain name in json response of invoice endpoint --- app/controllers/api/v1/invoices_controller.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/v1/invoices_controller.rb b/app/controllers/api/v1/invoices_controller.rb index 711427e85..a7c608513 100644 --- a/app/controllers/api/v1/invoices_controller.rb +++ b/app/controllers/api/v1/invoices_controller.rb @@ -14,10 +14,10 @@ def index @deposit_paid = current_user.domain_participate_auctions.includes(:auction).order(created_at: :desc) render json: { - issued_invoices: @issued_invoices, - paid_invoices: @paid_invoices, - cancelled_payable_invoices: @cancelled_payable_invoices, - cancelled_expired_invoices: @cancelled_expired_invoices, + issued_invoices: invoices_with_auction_name(@issued_invoices), + paid_invoices: invoices_with_auction_name(@paid_invoices), + cancelled_payable_invoices: invoices_with_auction_name(@cancelled_payable_invoices), + cancelled_expired_invoices: invoices_with_auction_name(@cancelled_expired_invoices), deposit_paid: @deposit_paid.map do |dpa| { id: dpa.id, @@ -25,7 +25,8 @@ def index status: dpa.status, invoice_number: dpa.invoice_number, domain_name: dpa.auction.domain_name, - auction_id: dpa.auction_id + auction_id: dpa.auction_id, + auction_name: dpa.auction.domain_name } end } @@ -35,10 +36,17 @@ def index def invoices_list_by_status(status) Invoice.accessible_by(current_ability) + .includes(result: :auction) .where(user_id: current_user.id) .where(status:) .order(due_date: :desc) end + + def invoices_with_auction_name(invoices) + invoices.map do |invoice| + invoice.as_json.merge(auction_name: invoice.result&.auction&.domain_name) + end + end end end end From 2a79e84034e8216bee84a27918caf35938b1ccde Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 2 Dec 2025 15:11:18 +0200 Subject: [PATCH 45/45] updated gemfile.lock --- Gemfile.lock | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 735c3d7fe..7640e82f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -89,8 +89,8 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) aes_key_wrap (1.1.0) airbrake (13.0.5) airbrake-ruby (~> 6.0) @@ -101,7 +101,7 @@ GEM attr_required (1.0.2) base64 (0.3.0) bcrypt (3.1.20) - benchmark (0.4.1) + benchmark (0.5.0) bigdecimal (3.3.1) bindata (2.5.1) bindex (0.8.1) @@ -129,7 +129,7 @@ GEM logger (~> 1.5) coderay (1.1.3) concurrent-ruby (1.3.5) - connection_pool (2.5.4) + connection_pool (2.5.5) crack (1.0.0) bigdecimal rexml @@ -278,13 +278,13 @@ GEM faraday-follow_redirects jsonapi-serializer (2.2.0) activesupport (>= 4.2) - addressable (~> 2.8) - childprocess (~> 5.0) - logger (~> 1.6) jwt (2.10.2) base64 language_server-protocol (3.17.0.5) launchy (3.1.1) + addressable (~> 2.8) + childprocess (~> 5.0) + logger (~> 1.6) letter_opener (1.10.0) launchy (>= 2.2, < 4) letter_opener_web (3.0.0) @@ -321,7 +321,7 @@ GEM rake mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.26.0) + minitest (5.26.2) money (6.19.0) i18n (>= 0.6.4, <= 2) msgpack (1.8.0) @@ -415,7 +415,7 @@ GEM psych (5.2.6) date stringio - public_suffix (6.0.2) + public_suffix (7.0.0) puma (7.1.0) nio4r (~> 2.0) racc (1.8.1) @@ -608,7 +608,7 @@ GEM unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.1.0) - uri (1.0.4) + uri (1.1.1) useragent (0.16.11) validate_url (1.0.15) activemodel (>= 3.0.0) @@ -621,13 +621,9 @@ GEM warden (1.2.9) rack (>= 2.0.9) warden-jwt_auth (0.8.0) - actionview (>= 6.0.0) - activemodel (>= 6.0.0) - bindex (>= 0.4.0) dry-auto_inject (>= 0.8, < 2) dry-configurable (>= 0.13, < 2) jwt (~> 2.1) - railties (>= 6.0.0) warden (~> 1.2) web-console (4.2.1) actionview (>= 6.0.0)