diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..bea438e --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.1 diff --git a/backend/app/controllers/api/v1/handle_file_controller.rb b/backend/app/controllers/api/v1/handle_file_controller.rb new file mode 100644 index 0000000..5450ce3 --- /dev/null +++ b/backend/app/controllers/api/v1/handle_file_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Api + module V1 + class HandleFileController < Api::ApplicationController + include HandleFileHelper + + def import + rows = set_rows_to_json(params[:file].tempfile) + render json: { file_rows: rows }, status: :ok + end + end + end +end diff --git a/backend/app/helpers/handle_file_helper.rb b/backend/app/helpers/handle_file_helper.rb new file mode 100644 index 0000000..72ab95c --- /dev/null +++ b/backend/app/helpers/handle_file_helper.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "csv" + +module HandleFileHelper + def rows_to_json(file) + file_parsed = parse_csv(file) + build_response(file_parsed) + end + + def parse_csv(file) + CSV.parse(file.read) + end + + def encode_string(string) + string.force_encoding("ISO-8859-1").encode("UTF-8") + end + + def build_response(parsed_file) + json_response = {} + parsed_file.each_with_index do |row, index| + json_response["row_#{index + 1}"] = encode_string(row.join(",")) + end + + json_response + end +end diff --git a/backend/config/application.rb b/backend/config/application.rb index 8d2e009..4e9eaf9 100644 --- a/backend/config/application.rb +++ b/backend/config/application.rb @@ -3,6 +3,7 @@ require_relative "boot" require "rails/all" +require "csv" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. diff --git a/backend/config/routes.rb b/backend/config/routes.rb index aa9596d..339069e 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -18,6 +18,7 @@ namespace :api, defaults: { format: :json } do namespace :v1 do + post "/import", to: "handle_file#import" resources :users, only: %i[index create] resources :schools, only: %i[index] end diff --git a/backend/spec/fixtures/files/empty_file.csv b/backend/spec/fixtures/files/empty_file.csv new file mode 100644 index 0000000..e69de29 diff --git a/backend/spec/fixtures/files/non_csv_file.txt b/backend/spec/fixtures/files/non_csv_file.txt new file mode 100644 index 0000000..e69de29 diff --git a/backend/spec/fixtures/files/valid_file.csv b/backend/spec/fixtures/files/valid_file.csv new file mode 100644 index 0000000..7f8aa6e --- /dev/null +++ b/backend/spec/fixtures/files/valid_file.csv @@ -0,0 +1,3 @@ +name,age +Alice,30 +Bob,25 diff --git a/backend/spec/requests/api/v1/handle_file_spec.rb b/backend/spec/requests/api/v1/handle_file_spec.rb new file mode 100644 index 0000000..034e9e2 --- /dev/null +++ b/backend/spec/requests/api/v1/handle_file_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "HandleFiles" do + describe "POST api/v1/import" do + context "when CSV file is valid" do + let(:csv_file) { fixture_file_upload(Rails.root.join("spec/fixtures/files/valid_file.csv"), "text/csv") } + + it "returns status ok" do + post "/api/v1/import", params: { file: csv_file } + expect(response).to have_http_status(:ok) + end + + it "returns the correct JSON response with file rows" do + post "/api/v1/import", params: { file: csv_file } + expect(response.parsed_body["file_rows"]).to be_a(Hash) + end + end + + context "when CSV file is empty" do + let(:empty_csv_file) { fixture_file_upload(Rails.root.join("spec/fixtures/files/empty_file.csv"), "text/csv") } + + it "returns status ok" do + post "/api/v1/import", params: { file: empty_csv_file } + expect(response).to have_http_status(:ok) + end + + it "returns empty file rows in JSON" do + post "/api/v1/import", params: { file: empty_csv_file } + expect(response.parsed_body["file_rows"]).to be_empty + end + end + + context "when file is not a CSV" do + let(:non_csv_file) { fixture_file_upload(Rails.root.join("spec/fixtures/files/non_csv_file.txt"), "text/csv") } + + it "returns status ok" do + post "/api/v1/import", params: { file: non_csv_file } + expect(response).to have_http_status(:ok) + end + + it "returns the correct JSON response with file rows" do + post "/api/v1/import", params: { file: non_csv_file } + expect(response.body).to include("file_rows") + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..52b6b14 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "sidekiq/web" + +Rails.application.routes.draw do + mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" if Rails.env.development? + post "/graphql", to: "graphql#execute" + mount Sidekiq::Web => "/sidekiq" + mount Rswag::Ui::Engine => "/api-docs" + mount Rswag::Api::Engine => "/api-docs" + devise_for :users + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html + + # Defines the root path route ("/") + # root "articles#index" + + get "/public_method", to: "hello_world#public_method" + get "/private_method", to: "hello_world#private_method" + get "/search", to: "hello_world#search" + + namespace :api, defaults: { format: :json } do + namespace :v1 do + get "/public_method", to: "hello_world#public_method" + get "/private_method", to: "hello_world#private_method" + post "/import", to: "handle_file#import" + + resources :users, only: [:index] + end + end +end