diff --git a/app/controllers/api/v1/post_interactions_controller.rb b/app/controllers/api/v1/post_interactions_controller.rb new file mode 100644 index 000000000..f52418a8c --- /dev/null +++ b/app/controllers/api/v1/post_interactions_controller.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Api + module V1 + class PostInteractionsController < Api::V1::BaseController + include PostsHelper + + before_action do + require_access_token %w[read write] + end + + rescue_from ActiveRecord::RecordNotFound do + render json: I18n.t("api.endpoint_errors.posts.post_not_found"), status: :not_found + end + + def subscribe + post = post_service.find!(params[:post_id]) + current_user.participate!(post) + head :no_content + rescue ActiveRecord::RecordInvalid + render json: I18n.t("api.endpoint_errors.interactions.cant_subscribe"), status: :unprocessable_entity + end + + def hide + post = post_service.find!(params[:post_id]) + current_user.toggle_hidden_shareable(post) + head :no_content + end + + def mute + post = post_service.find!(params[:post_id]) + participation = current_user.participations.find_by!(target_id: post.id) + participation.destroy + head :no_content + end + + def report + reason = params.require(:reason) + post = post_service.find!(params[:post_id]) + report = current_user.reports.new( + item_id: post.id, + item_type: "Post", + text: reason + ) + if report.save + head :no_content + else + render json: I18n.t("api.endpoint_errors.posts.cant_report"), status: :conflict + end + rescue ActionController::ParameterMissing + render json: I18n.t("api.endpoint_errors.posts.cant_report"), status: :unprocessable_entity + end + + private + + def post_service + @post_service ||= PostService.new(current_user) + end + end + end +end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 49c29da18..9b72c8694 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -972,10 +972,13 @@ en: user_not_allowed_to_like: "User is not allowed to like" like_exists: "Like already exists" no_like: "Like doesn’t exist" + interactions: + cant_subscribe: "Can't subscribe to this post" posts: post_not_found: "Post with provided guid could not be found" failed_create: "Failed to create the post" failed_delete: "Not allowed to delete the post" + cant_report: "Failed to create report on this post" tags: cant_process: "Failed to process the tag followings request" diff --git a/config/routes.rb b/config/routes.rb index b5545ea37..c64d0cd33 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -231,6 +231,10 @@ Rails.application.routes.draw do end resource :reshares, only: %i[show create] resource :likes, only: %i[show create destroy] + post "subscribe" => "post_interactions#subscribe" + post "mute" => "post_interactions#mute" + post "hide" => "post_interactions#hide" + post "report" => "post_interactions#report" end resources :conversations, only: %i[show index create destroy] do resources :messages, only: %i[index create] diff --git a/spec/integration/api/post_interactions_controller_spec.rb b/spec/integration/api/post_interactions_controller_spec.rb new file mode 100644 index 000000000..cb9f0af0b --- /dev/null +++ b/spec/integration/api/post_interactions_controller_spec.rb @@ -0,0 +1,294 @@ +# frozen_sTring_literal: true + +require "spec_helper" + +describe Api::V1::PostInteractionsController do + let(:auth) { FactoryGirl.create(:auth_with_read_and_write) } + let!(:access_token) { auth.create_access_token.to_s } + let(:auth_read_only) { FactoryGirl.create(:auth_with_read) } + let!(:access_token_read_only) { auth_read_only.create_access_token.to_s } + + before do + @status = alice.post( + :status_message, + text: "hello @{#{bob.diaspora_handle}} and @{#{eve.diaspora_handle}}from Alice!", + public: true, + to: "all" + ) + end + + describe "#subscribe" do + context "succeeds" do + it "with proper guid and access token" do + participation_count = @status.participations.count + post( + api_v1_post_subscribe_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(204) + expect(@status.participations.count).to eq(participation_count + 1) + end + end + + context "fails" do + it "when duplicate" do + post( + api_v1_post_subscribe_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(204) + post( + api_v1_post_subscribe_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(422) + end + + it "with improper guid" do + post( + api_v1_post_subscribe_path("999_999_999"), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + + it "with read only token" do + post( + api_v1_post_subscribe_path(@status.guid), + params: { + access_token: access_token_read_only + } + ) + expect(response.status).to eq(403) + end + + it "with invalid token" do + post( + api_v1_post_subscribe_path(@status.guid), + params: { + access_token: "999_999_999" + } + ) + expect(response.status).to eq(401) + end + end + end + + describe "#hide" do + context "succeeds" do + it "with proper guid and access token" do + hidden_count = auth.user.hidden_shareables.count + post( + api_v1_post_hide_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(204) + expect(auth.user.reload.hidden_shareables.count).to eq(hidden_count + 1) + end + end + + context "fails" do + it "with improper guid" do + post( + api_v1_post_hide_path("999_999_999"), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + + it "with read only token" do + post( + api_v1_post_hide_path(@status.guid), + params: { + access_token: access_token_read_only + } + ) + expect(response.status).to eq(403) + end + + it "with invalid token" do + post( + api_v1_post_hide_path(@status.guid), + params: { + access_token: "999_999_999" + } + ) + expect(response.status).to eq(401) + end + end + end + + describe "#mute" do + before do + post( + api_v1_post_subscribe_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(204) + end + + context "succeeds" do + it "with proper guid and access token" do + count = @status.participations.count + post( + api_v1_post_mute_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(204) + expect(@status.participations.count).to eq(count - 1) + end + end + + context "fails" do + it "with improper guid" do + post( + api_v1_post_mute_path("999_999_999"), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + + it "when not subscribed already" do + post( + api_v1_post_mute_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(204) + post( + api_v1_post_mute_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(404) + end + + it "with read only token" do + post( + api_v1_post_mute_path(@status.guid), + params: { + access_token: access_token_read_only + } + ) + expect(response.status).to eq(403) + end + + it "with invalid token" do + post( + api_v1_post_mute_path(@status.guid), + params: { + access_token: "999_999_999" + } + ) + expect(response.status).to eq(401) + end + end + end + + describe "#report" do + context "succeeds" do + it "with proper guid and access token" do + report_count = @status.reports.count + post( + api_v1_post_report_path(@status.guid), + params: { + reason: "My reason", + access_token: access_token + } + ) + expect(response.status).to eq(204) + expect(@status.reports.count).to eq(report_count + 1) + end + end + + context "fails" do + it "with improper guid" do + post( + api_v1_post_report_path("999_999_999"), + params: { + reason: "My reason", + access_token: access_token + } + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + + it "when already reported" do + post( + api_v1_post_report_path(@status.guid), + params: { + reason: "My reason", + access_token: access_token + } + ) + expect(response.status).to eq(204) + post( + api_v1_post_report_path(@status.guid), + params: { + reason: "My reason", + access_token: access_token + } + ) + expect(response.status).to eq(409) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.cant_report")) + end + + it "when missing reason" do + post( + api_v1_post_report_path(@status.guid), + params: { + access_token: access_token + } + ) + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.cant_report")) + end + + it "with read only token" do + post( + api_v1_post_report_path(@status.guid), + params: { + reason: "My reason", + access_token: access_token_read_only + } + ) + expect(response.status).to eq(403) + end + + it "with invalid token" do + post( + api_v1_post_report_path(@status.guid), + params: { + reason: "My reason", + access_token: "999_999_999" + } + ) + expect(response.status).to eq(401) + end + end + end +end