Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
/.idea
/.claude
CLAUDE.md
.byebug_history
1 change: 1 addition & 0 deletions .ruby-gemset
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CL_test_assignment
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby-3.4
3.4.6
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ gem "thruster", require: false
# CSV processing for seed data
gem "csv"

gem 'active_interaction'
gem 'memery'

group :development, :test do
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
gem "brakeman", require: false
gem "rubocop-rails-omakase", require: false

gem 'factory_bot_rails'
gem 'byebug'
gem 'rspec-rails'
end

8 changes: 8 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ GEM
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_interaction (5.5.0)
activemodel (>= 5.2, < 9)
activesupport (>= 5.2, < 9)
activejob (8.0.2.1)
activesupport (= 8.0.2.1)
globalid (>= 0.3.6)
Expand Down Expand Up @@ -82,6 +85,7 @@ GEM
brakeman (7.1.0)
racc
builder (3.3.0)
byebug (12.0.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crass (1.0.6)
Expand Down Expand Up @@ -139,6 +143,7 @@ GEM
net-pop
net-smtp
marcel (1.0.4)
memery (1.8.0)
mini_mime (1.1.5)
minitest (5.25.5)
msgpack (1.8.0)
Expand Down Expand Up @@ -340,12 +345,15 @@ PLATFORMS
x86_64-linux-musl

DEPENDENCIES
active_interaction
bootsnap
brakeman
byebug
csv
debug
factory_bot_rails
kamal
memery
pg (>= 1.1)
puma (>= 5.0)
rails (~> 8.0.2, >= 8.0.2.1)
Expand Down
44 changes: 44 additions & 0 deletions app/controllers/api/v1/available_routes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module Api
module V1
class AvailableRoutesController < BaseController
def index
result = [
{
"origin_iata" => "UUS",
"destination_iata" => "DME",
"departure_time" => "2024-01-01T05:45:00.000Z",
"arrival_time" => "2024-01-02T18:05:00.000Z",
"segments" => [
{
"carrier" => "S7",
"segment_number" => "6224",
"origin_iata" => "UUS",
"destination_iata" => "VVO",
"std" => "2024-01-01T05:45:00.000Z",
"sta" => "2024-01-01T07:40:00.000Z"
},
{
"carrier" => "S7",
"segment_number" => "5202",
"origin_iata" => "VVO",
"destination_iata" => "OVB",
"std" => "2024-01-01T20:25:00.000Z",
"sta" => "2024-01-02T02:30:00.000Z"
},
{
"carrier" => "S7",
"segment_number" => "2514",
"origin_iata" => "OVB",
"destination_iata" => "DME",
"std" => "2024-01-02T13:40:00.000Z",
"sta" => "2024-01-02T18:05:00.000Z"
}
]
}
]

render json: result, status: :ok
end
end
end
end
6 changes: 6 additions & 0 deletions app/controllers/api/v1/base_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Api
module V1
class BaseController < ActionController::API
end
end
end
94 changes: 94 additions & 0 deletions app/interactors/api/v1/available_routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
module Api::V1
class AvailableRoutes < BaseInteraction
string :carrier
string :origin_iata
string :destination_iata
string :departure_from
string :departure_to

def execute
response
end

private

def transfer_iata
PermittedRoute
.find_by(carrier:, origin_iata:, destination_iata:)
.transfer_iata_codes
.flat_map { _1.length == 6 ? [_1[0,3], _1[3,3]] : _1[0,3] }.uniq
end

def origin_segments
Segment.where(airline: carrier, origin_iata:, destination_iata: transfer_iata).order(:sta)
end
memoize :origin_segments

def connection_segments
Segment.where(airline: carrier, origin_iata: transfer_iata, destination_iata: transfer_iata).order(:sta)
end
memoize :connection_segments

def destination_segments
Segment.where(airline: carrier, origin_iata: transfer_iata, destination_iata:).order(:sta)
end
memoize :destination_segments

def segment_hash(segment)
{
'carrier' => segment.airline,
'segment_number' => segment.segment_number,
'origin_iata' => segment.origin_iata,
'destination_iata' => segment.destination_iata,
'std' => segment.std.iso8601(3),
'sta' => segment.sta.iso8601(3)
}
end

def total_segment(origin_segment, destination_segment, segments)
{
"origin_iata" => origin_segment.origin_iata,
"destination_iata" => destination_segment.destination_iata,
"departure_time" => origin_segment.std.iso8601(3),
"arrival_time" => destination_segment.sta.iso8601(3),
"segments" => segments
}
end

def response
total_segments = []
origin_segments.each do |origin_segment|
destination_segments.each do |destination_segment|
transfers = connection_segments.select { origin_segment.destination_iata == _1.origin_iata || _1.destination_iata == destination_segment.origin_iata }

if transfers.any? && transfers.any? { origin_segment.destination_iata == _1.origin_iata && _1.destination_iata == destination_segment.origin_iata }
transfers.each do |transfer|
next if origin_segment.std > transfer.sta
next if transfer.std > destination_segment.sta
next if (transfer.sta - transfer.std) / 1.minute > MAX_CONNECTION_TIME
next if (transfer.sta - transfer.std) / 1.minute <= MIN_CONNECTION_TIME

segments = []
segments << segment_hash(origin_segment)
segments << segment_hash(transfer)
segments << segment_hash(destination_segment)

total_segments << total_segment(origin_segment, destination_segment, segments)
end
else
next if origin_segment.std > destination_segment.sta
next if origin_segment.destination_iata != destination_segment.origin_iata

segments = []
segments << segment_hash(origin_segment)
segments << segment_hash(destination_segment)

total_segments << total_segment(origin_segment, destination_segment, segments)
end
end
end

total_segments
end
end
end
3 changes: 3 additions & 0 deletions app/interactors/base_interaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class BaseInteraction < ActiveInteraction::Base
include Memery
end
3 changes: 3 additions & 0 deletions config/initializers/constants.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Константы времени пересадок
MIN_CONNECTION_TIME = ENV.fetch('MIN_CONNECTION_TIME', 480).to_i # минимальное время для пересадки, мин (8 часов)
MAX_CONNECTION_TIME = ENV.fetch('MAX_CONNECTION_TIME', 2880).to_i # максимальное время ожидания, мин (48 часов)
6 changes: 6 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check

namespace :api do
namespace :v1 do
resources :available_routes, only: %i[index]
end
end

# Defines the root path route ("/")
# root "posts#index"
end
30 changes: 30 additions & 0 deletions spec/controllers/api/v1/available_routes_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'rails_helper'

describe Api::V1::AvailableRoutesController, type: :controller do
describe '#index' do

context 'simple segment' do
let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[OVB]) }
let!(:segment1) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB') }
let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME') }

subject(:send_request) do
get :index, params: {
carrier: 'S7',
origin_iata: 'UUS',
destination_iata: 'DME',
departure_from: '2024-01-01',
departure_to: '2024-01-07',
}
end

it 'return status 200' do
expect(subject).to have_http_status :ok
end

it 'return not empty body' do
expect(subject.body).to_not be_empty
end
end
end
end
11 changes: 11 additions & 0 deletions spec/factories/permitted_route.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FactoryBot.define do
factory :permitted_route do
sequence(:id)

carrier { 'S7' }
origin_iata { 'UUS' }
destination_iata { 'DME' }
direct { true }
transfer_iata_codes { %w[OVB KHV IKT VVOOVB] }
end
end
12 changes: 12 additions & 0 deletions spec/factories/segments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FactoryBot.define do
factory :segment do
sequence(:id)

airline { 'S7' }
segment_number { '0321' }
origin_iata { 'UUS' }
destination_iata { 'DME' }
std { Time.new(2024, 1, 1, 5, 45, 0) }
sta { Time.new(2024, 1, 2, 18, 5, 0) }
end
end
Loading