diff --git a/app/controllers/repository_prefixes_controller.rb b/app/controllers/repository_prefixes_controller.rb new file mode 100644 index 000000000..f0704a8e2 --- /dev/null +++ b/app/controllers/repository_prefixes_controller.rb @@ -0,0 +1,140 @@ +require 'base32/url' +require 'uri' + +class RepositoryPrefixesController < ApplicationController + before_action :set_client_prefix, only: [:show, :update, :destroy] + before_action :authenticate_user! + before_action :set_include + + def index + # support nested routes + if params[:id].present? + collection = ClientPrefix.where(id: params[:id]) + elsif params[:client_id].present? && params[:prefix_id].present? + collection = ClientPrefix.joins(:client, :prefix).where('datacentre.symbol = ?', params[:client_id]).where('prefix.prefix = ?', params[:prefix_id]) + elsif params[:client_id].present? + client = Client.where('datacentre.symbol = ?', params[:client_id]).first + collection = client.present? ? client.client_prefixes.joins(:prefix) : ClientPrefix.none + elsif params[:prefix_id].present? + prefix = Prefix.where('prefix.prefix = ?', params[:prefix_id]).first + collection = prefix.present? ? prefix.client_prefixes.joins(:client) : ClientPrefix.none + else + collection = ClientPrefix.joins(:client, :prefix) + end + + collection = collection.query(params[:query]) if params[:query].present? + collection = collection.where('YEAR(datacentre_prefixes.created_at) = ?', params[:year]) if params[:year].present? + + if params[:year].present? + years = [{ id: params[:year], + title: params[:year], + count: collection.where('YEAR(datacentre_prefixes.created_at) = ?', params[:year]).count }] + else + years = collection.where.not(prefixes: nil).order("YEAR(datacentre_prefixes.created_at) DESC").group("YEAR(datacentre_prefixes.created_at)").count + years = years.map { |k,v| { id: k.to_s, title: k.to_s, count: v } } + end + + page = page_from_params(params) + total = collection.count + + order = case params[:sort] + when "name" then "prefix.prefix" + when "-name" then "prefix.prefix DESC" + when "created" then "datacentre_prefixes.created_at" + else "datacentre_prefixes.created_at DESC" + end + + @client_prefixes = collection.order(order).page(page[:number]).per(page[:size]) + + options = {} + options[:meta] = { + total: total, + "totalPages" => @client_prefixes.total_pages, + page: page[:number].to_i, + years: years + }.compact + + options[:links] = { + self: request.original_url, + next: @client_prefixes.blank? ? nil : request.base_url + "/client-prefixes?" + { + query: params[:query], + year: params[:year], + "page[number]" => params.dig(:page, :number).to_i + 1, + "page[size]" => params.dig(:page, :size), + sort: params[:sort] }.compact.to_query + }.compact + options[:include] = @include + options[:is_collection] = true + + render json: RepositoryPrefixSerializer.new(@client_prefixes, options).serialized_json, status: :ok + end + + def show + authorize! :show, @client_prefix + options = {} + options[:include] = @include + options[:is_collection] = false + + render json: RepositoryPrefixSerializer.new(@client_prefix, options).serialized_json, status: :ok + end + + def create + logger = Logger.new(STDOUT) + @client_prefix = ClientPrefix.new(safe_params) + authorize! :create, @client_prefix + + if @client_prefix.save + options = {} + options[:include] = @include + options[:is_collection] = false + + render json: RepositoryPrefixSerializer.new(@client_prefix, options).serialized_json, status: :created + else + logger.warn @client_prefix.errors.inspect + render json: serialize_errors(@client_prefix.errors), status: :unprocessable_entity + end + end + + def update + authorize! :update, @client_prefix + response.headers["Allow"] = "HEAD, GET, POST, DELETE, OPTIONS" + render json: { errors: [{ status: "405", title: "Method not allowed" }] }.to_json, status: :method_not_allowed + end + + def destroy + authorize! :destroy, @client_prefix + @client_prefix.destroy + head :no_content + end + + protected + + def set_include + if params[:include].present? + @include = params[:include].split(",").map { |i| i.downcase.underscore.to_sym } + @include = @include & [:repository, :prefix, :provider_prefix, :provider] + else + # always include because Ember pagination doesn't (yet) understand include parameter + @include = [:repository, :prefix, :provider_prefix, :provider] + end + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_client_prefix + id = Base32::URL.decode(URI.decode(params[:id])) + fail ActiveRecord::RecordNotFound unless id.present? + + @client_prefix = ClientPrefix.where(id: id.to_i).first + + fail ActiveRecord::RecordNotFound unless @client_prefix.present? + end + + def safe_params + ActiveModelSerializers::Deserialization.jsonapi_parse!( + params, only: [:id, :repository, :prefix, :providerPrefix], + keys: { repository: :client, "providerPrefix" => :provider_prefix } + ) + end +end diff --git a/app/models/client_prefix.rb b/app/models/client_prefix.rb index 5d0cc5cab..8e7368298 100644 --- a/app/models/client_prefix.rb +++ b/app/models/client_prefix.rb @@ -40,6 +40,18 @@ def client_id=(value) self.datacentre = r.id end + def repository_id + client_symbol.downcase + end + + # workaround for non-standard database column names and association + def repository_id=(value) + r = ::Client.where(symbol: value).first + fail ActiveRecord::RecordNotFound unless r.present? + + self.datacentre = r.id + end + def prefix_id prefix.prefix end diff --git a/app/serializers/repository_prefix_serializer.rb b/app/serializers/repository_prefix_serializer.rb new file mode 100644 index 000000000..1bf280213 --- /dev/null +++ b/app/serializers/repository_prefix_serializer.rb @@ -0,0 +1,14 @@ +class RepositoryPrefixSerializer + include FastJsonapi::ObjectSerializer + set_key_transform :camel_lower + set_type "repository-prefixes" + set_id :uid + cache_options enabled: true, cache_length: 24.hours + + attributes :created, :updated + + belongs_to :repository, object_method_name: :client, id_method_name: :client_id, record_type: :repositories + belongs_to :provider, record_type: :providers + belongs_to :provider_prefix, record_type: :provider_prefixes + belongs_to :prefix, record_type: :prefixes +end diff --git a/config/routes.rb b/config/routes.rb index d86a3032b..89a4e095a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -104,6 +104,7 @@ resources :prefixes, constraints: { :id => /.+/ } end resources :providers, constraints: { :id => /.+/ } + resources :repository_prefixes, path: "repository-prefixes" resources :resource_types, path: 'resource-types', only: [:show, :index] # custom routes for maintenance tasks diff --git a/spec/requests/client_prefixes_spec.rb b/spec/requests/client_prefixes_spec.rb index 4f75303fc..8753e5802 100644 --- a/spec/requests/client_prefixes_spec.rb +++ b/spec/requests/client_prefixes_spec.rb @@ -53,7 +53,7 @@ let(:valid_attributes) do { "data" => { - "type" => "provider-prefixes", + "type" => "client-prefixes", "relationships": { "client": { "data":{ diff --git a/spec/requests/repository_prefixes_spec.rb b/spec/requests/repository_prefixes_spec.rb new file mode 100644 index 000000000..eb4513ca1 --- /dev/null +++ b/spec/requests/repository_prefixes_spec.rb @@ -0,0 +1,107 @@ +require 'rails_helper' + +describe "Repository Prefixes", type: :request do + let!(:client_prefixes) { create_list(:client_prefix, 5) } + let(:client_prefix) { create(:client_prefix) } + let(:bearer) { User.generate_token(role_id: "staff_admin") } + let(:headers) { {'HTTP_ACCEPT'=>'application/vnd.api+json', 'HTTP_AUTHORIZATION' => 'Bearer ' + bearer }} + + describe 'GET /repository-prefixes' do + it 'returns repository-prefixes' do + get '/repository-prefixes', nil, headers + + expect(last_response.status).to eq(200) + expect(json['data'].size).to eq(5) + end + end + + describe 'GET /repository-prefixes/:uid' do + context 'when the record exists' do + it 'returns the repository-prefix' do + get "/repository-prefixes/#{client_prefix.uid}", nil, headers + + expect(last_response.status).to eq(200) + expect(json.dig("data", "id")).to eq(client_prefix.uid) + end + end + + context 'when the record does not exist' do + it 'returns status code 404' do + get "/repository-prefixes/xxx", nil, headers + + expect(last_response.status).to eq(404) + expect(json["errors"].first).to eq("status"=>"404", "title"=>"The resource you are looking for doesn't exist.") + end + end + end + + describe 'PATCH /repository-prefixes/:uid' do + it 'returns method not supported error' do + patch "/repository-prefixes/#{client_prefix.uid}", nil, headers + + expect(last_response.status).to eq(405) + expect(json.dig("errors")).to eq([{"status"=>"405", "title"=>"Method not allowed"}]) + end + end + + describe 'POST /repository-prefixes' do + context 'when the request is valid' do + let(:provider) { create(:provider) } + let(:client) { create(:client, provider: provider) } + let(:prefix) { create(:prefix) } + let(:provider_prefix) { create(:provider_prefix, provider: provider, prefix: prefix) } + let(:valid_attributes) do + { + "data" => { + "type" => "repository-prefixes", + "relationships": { + "repository": { + "data":{ + "type": "repositories", + "id": client.symbol.downcase + } + }, + "provider-prefix": { + "data":{ + "type": "provider-prefixes", + "id": provider_prefix.prefix + } + }, + "prefix": { + "data":{ + "type": "prefixes", + "id": prefix.prefix + } + } + } + } + } + end + + it 'creates a repository-prefix' do + post '/repository-prefixes', valid_attributes, headers + + expect(last_response.status).to eq(201) + expect(json.dig('data', 'id')).not_to be_nil + end + end + + context 'when the request is invalid' do + let!(:client) { create(:client) } + let(:not_valid_attributes) do + { + "data" => { + "type" => "repository-prefixes" + } + } + end + + it 'returns status code 422' do + post '/repository-prefixes', not_valid_attributes, headers + + expect(last_response.status).to eq(422) + expect(json["errors"].first).to eq("source"=>"client", "title"=>"Must exist") + end + end + end +end