diff --git a/app/controllers/api/v1/aspects_controller.rb b/app/controllers/api/v1/aspects_controller.rb new file mode 100644 index 000000000..066fb8bbe --- /dev/null +++ b/app/controllers/api/v1/aspects_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module Api + module V1 + class AspectsController < Api::V1::BaseController + before_action only: %i[index show] do + require_access_token %w[read] + end + + before_action only: %i[create update destroy] do + require_access_token %w[read write] + end + + def index + aspects = current_user.aspects.map {|a| AspectPresenter.new(a).as_api_json(false) } + render json: aspects + end + + def show + aspect = current_user.aspects.where(id: params[:id]).first + if aspect + render json: AspectPresenter.new(aspect).as_api_json(true) + else + render json: I18n.t("api.endpoint_errors.aspects.not_found"), status: :not_found + end + end + + def create + params.require(%i[name chat_enabled]) + aspect = current_user.aspects.build(name: params[:name], chat_enabled: params[:chat_enabled]) + if aspect&.save + render json: AspectPresenter.new(aspect).as_api_json(true) + else + render json: I18n.t("api.endpoint_errors.aspects.cant_create"), status: :unprocessable_entity + end + rescue ActionController::ParameterMissing + render json: I18n.t("api.endpoint_errors.aspects.cant_create"), status: :unprocessable_entity + end + + def update + aspect = current_user.aspects.where(id: params[:id]).first + + if !aspect + render json: I18n.t("api.endpoint_errors.aspects.cant_update"), status: :not_found + elsif aspect.update!(aspect_params(true)) + render json: AspectPresenter.new(aspect).as_api_json(true) + else + render json: I18n.t("api.endpoint_errors.aspects.cant_update"), status: :unprocessable_entity + end + rescue ActionController::ParameterMissing, ActiveRecord::RecordInvalid + render json: I18n.t("api.endpoint_errors.aspects.cant_update"), status: :unprocessable_entity + end + + def destroy + aspect = current_user.aspects.where(id: params[:id]).first + if aspect&.destroy + head :no_content + else + render json: I18n.t("api.endpoint_errors.aspects.cant_delete"), status: :unprocessable_entity + end + end + + private + + def aspect_params(allow_order=false) + parameters = params.permit(:name, :chat_enabled) + parameters[:order_id] = params[:order] if params.has_key?(:order) && allow_order + parameters + end + end + end +end diff --git a/app/presenters/aspect_presenter.rb b/app/presenters/aspect_presenter.rb index 5f2af2050..726ec505a 100644 --- a/app/presenters/aspect_presenter.rb +++ b/app/presenters/aspect_presenter.rb @@ -11,7 +11,18 @@ class AspectPresenter < BasePresenter } end - def to_json(options = {}) + def as_api_json(full=false) + values = { + id: @aspect.id, + name: @aspect.name, + order: @aspect.order_id + } + + values[:chat_enabled] = @aspect.chat_enabled if full + values + end + + def to_json(options={}) as_json.to_json(options) end -end \ No newline at end of file +end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 8ff068b06..35caedccf 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -951,6 +951,11 @@ en: login_required: "You must first login before you can authorize this application" could_not_authorize: "The application could not be authorized" endpoint_errors: + aspects: + cant_create: "Failed to create the aspect" + cant_delete: "Failed to delete the aspect" + cant_update: "Failed to update the aspect" + not_found: "Aspect with provided ID could not be found" conversations: not_found: "Conversation with provided guid could not be found" cant_process: "Couldn’t accept or process the conversation" diff --git a/config/routes.rb b/config/routes.rb index eb24be5fb..d90f5bf4d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -222,6 +222,7 @@ Rails.application.routes.draw do get "podmin", to: "home#podmin" api_version(module: "Api::V1", path: {value: "api/v1"}) do + resources :aspects, only: %i[show index create destroy update] resources :posts, only: %i[show create destroy] do resources :comments, only: %i[create index destroy] do post "report" => "comments#report" @@ -232,6 +233,7 @@ Rails.application.routes.draw do resources :conversations, only: %i[show index create destroy] do resources :messages, only: %i[index create] end + get "streams/activity" => "streams#activity", :as => "activity_stream" get "streams/main" => "streams#multi", :as => "stream" get "streams/tags" => "streams#followed_tags", :as => "followed_tags_stream" diff --git a/spec/integration/api/aspects_controller_spec.rb b/spec/integration/api/aspects_controller_spec.rb new file mode 100644 index 000000000..0d97290e8 --- /dev/null +++ b/spec/integration/api/aspects_controller_spec.rb @@ -0,0 +1,302 @@ +# frozen_sTring_literal: true + +require "spec_helper" + +describe Api::V1::AspectsController do + let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let!(:access_token) { auth.create_access_token.to_s } + let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + + before do + @aspect1 = auth.user.aspects.where(name: "generic").first + @aspect2 = auth.user.aspects.create(name: "another aspect") + end + + describe "#index" do + it "returns list of aspects" do + get( + api_v1_aspects_path, + params: {access_token: access_token} + ) + expect(response.status).to eq(200) + aspects = JSON.parse(response.body) + expect(aspects.length).to eq(auth.user.aspects.length) + aspects.each do |aspect| + found_aspect = auth.user.aspects.find_by(id: aspect["id"]) + expect(aspect["name"]).to eq(found_aspect.name) + expect(aspect["order"]).to eq(found_aspect.order_id) + end + end + + it "fails if invalid token" do + get( + api_v1_aspects_path, + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + end + + describe "#show" do + context "with correct id" do + it "returns aspect" do + get( + api_v1_aspect_path(@aspect2.id), + params: {access_token: access_token} + ) + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["id"]).to eq(@aspect2.id) + expect(aspect["name"]).to eq(@aspect2.name) + expect(aspect["order"]).to eq(@aspect2.order_id) + expect(aspect["chat_enabled"]).to eq(@aspect2.chat_enabled) + end + end + + context "with incorrect id" do + it "fails to return with error" do + get( + api_v1_aspect_path("-1"), + params: {access_token: access_token} + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.not_found")) + end + end + + context "when not logged in" do + it "fails to return with error" do + get( + api_v1_aspect_path(@aspect2.id), + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + end + end + + describe "#create" do + context "with full aspect settings" do + it "creates aspect" do + new_name = "diaspora developers" + post( + api_v1_aspects_path, + params: {name: new_name, chat_enabled: true, access_token: access_token} + ) + + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["name"]).to eq(new_name) + expect(aspect["chat_enabled"]).to be_truthy + expect(aspect.has_key?("id")).to be_truthy + expect(aspect.has_key?("order")).to be_truthy + end + + it "fails to create duplicate aspect" do + post( + api_v1_aspects_path, + params: {name: @aspect1.name, chat_enabled: true, access_token: access_token} + ) + + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.cant_create")) + end + end + + context "with malformed settings" do + it "fails when missing name" do + post( + api_v1_aspects_path, + params: {chat_enabled: true, access_token: access_token} + ) + + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.cant_create")) + end + + it "fails when missing chat" do + post( + api_v1_aspects_path, + params: {name: "new_aspect", access_token: access_token} + ) + + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.cant_create")) + end + end + + context "improper credentials" do + it "fails when not logged in" do + post( + api_v1_aspects_path, + params: {name: "new_name", chat_enabled: true, access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + + it "fails when logged in read only" do + post( + api_v1_aspects_path, + params: {name: "new_name", chat_enabled: true, access_token: access_token_read_only} + ) + + expect(response.status).to eq(403) + end + end + end + + describe "#update" do + context "with aspect settings" do + it "updates full aspect" do + new_name = "NewAspectName" + new_chat = @aspect2.chat_enabled + order = @aspect2.order_id + 1 + patch( + api_v1_aspect_path(@aspect2.id), + params: {name: new_name, chat_enabled: new_chat, order: order, access_token: access_token} + ) + + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["name"]).to eq(new_name) + expect(aspect["chat_enabled"]).to eq(new_chat) + expect(aspect["order"]).to eq(order) + expect(aspect["id"]).to eq(@aspect2.id) + end + + it "updates name only aspect" do + new_name = "NewAspectName" + patch( + api_v1_aspect_path(@aspect2.id), + params: {name: new_name, access_token: access_token} + ) + + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["name"]).to eq(new_name) + expect(aspect["id"]).to eq(@aspect2.id) + end + + it "updates chat only" do + new_chat = @aspect2.chat_enabled + patch( + api_v1_aspect_path(@aspect2.id), + params: {chat_enabled: new_chat, access_token: access_token} + ) + + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["chat_enabled"]).to eq(new_chat) + expect(aspect["id"]).to eq(@aspect2.id) + end + + it "updates order only" do + order = @aspect2.order_id + 1 + patch( + api_v1_aspect_path(@aspect2.id), + params: {order: order, access_token: access_token} + ) + + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["order"]).to eq(order) + expect(aspect["id"]).to eq(@aspect2.id) + end + + it "succeds with no arguments" do + patch( + api_v1_aspect_path(@aspect2.id), + params: {access_token: access_token} + ) + + expect(response.status).to eq(200) + aspect = JSON.parse(response.body) + expect(aspect["name"]).to eq(@aspect2.name) + expect(aspect["chat_enabled"]).to eq(@aspect2.chat_enabled) + expect(aspect["id"]).to eq(@aspect2.id) + end + end + + context "with bad parameters" do + it "fails with reused name" do + patch( + api_v1_aspect_path(@aspect2.id), + params: {name: @aspect1.name, access_token: access_token} + ) + + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.cant_update")) + end + + it "fails with bad id" do + patch( + api_v1_aspect_path("-1"), + params: {name: "NewAspectName", access_token: access_token} + ) + + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.cant_update")) + end + end + + context "improper credentials" do + it "fails when not logged in" do + patch( + api_v1_aspect_path(@aspect2.id), + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + + it "fails when logged in read only" do + patch( + api_v1_aspect_path(@aspect2.id), + params: {access_token: access_token_read_only} + ) + expect(response.status).to eq(403) + end + end + end + + describe "#delete" do + context "with correct ID" do + it "deletes aspect" do + delete( + api_v1_aspect_path(@aspect2.id), + params: {access_token: access_token} + ) + expect(response.status).to eq(204) + end + end + + context "with bad ID" do + it "fails to delete with error" do + delete( + api_v1_aspect_path("-1"), + params: {access_token: access_token} + ) + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.aspects.cant_delete")) + end + end + + context "improper credentials" do + it "fails when not logged in" do + delete( + api_v1_aspect_path(@aspect2.id), + params: {access_token: "999_999_999"} + ) + expect(response.status).to eq(401) + end + + it "fails when logged in read only" do + delete( + api_v1_aspect_path(@aspect2.id), + params: {access_token: access_token_read_only} + ) + expect(response.status).to eq(403) + end + end + end +end diff --git a/spec/presenters/aspect_presenter_spec.rb b/spec/presenters/aspect_presenter_spec.rb index fd4d52830..5c332d054 100644 --- a/spec/presenters/aspect_presenter_spec.rb +++ b/spec/presenters/aspect_presenter_spec.rb @@ -2,7 +2,8 @@ describe AspectPresenter do before do - @presenter = AspectPresenter.new(bob.aspects.first) + @aspect = bob.aspects.first + @presenter = AspectPresenter.new(@aspect) end describe '#to_json' do @@ -10,4 +11,21 @@ describe AspectPresenter do expect(@presenter.to_json).to be_present end end -end \ No newline at end of file + + describe "#to_api_json" do + it "creates simple JSON" do + aspect_json = @presenter.as_api_json(false) + expect(aspect_json[:id]).to eq(@aspect.id) + expect(aspect_json[:name]).to eq(@aspect.name) + expect(aspect_json[:order]).to eq(@aspect.order_id) + end + + it "produces full JSON" do + aspect_json = @presenter.as_api_json(true) + expect(aspect_json[:id]).to eq(@aspect.id) + expect(aspect_json[:name]).to eq(@aspect.name) + expect(aspect_json[:order]).to eq(@aspect.order_id) + expect(aspect_json[:chat_enabled]).to eq(@aspect.chat_enabled) + end + end +end