Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
330 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# frozen_string_literal: true | ||
|
||
class Api::V1::CollaboratorsController < Api::V1::BaseController | ||
before_action :authenticate_user! | ||
before_action :set_project | ||
before_action :check_author_access, except: %i[index] | ||
before_action :check_view_access, only: %i[index] | ||
before_action :set_collaborator, only: %i[destroy] | ||
|
||
# /api/v1/projects/:project_id/collaborators | ||
def index | ||
@collaborators = paginate(@project.collaborators) | ||
# options for serializing collaborators | ||
@options = { | ||
params: { only_name: true }, | ||
links: link_attrs(@collaborators, api_v1_project_collaborators_url) | ||
} | ||
render json: Api::V1::UserSerializer.new(@collaborators, @options) | ||
end | ||
|
||
# POST /api/v1/projects/:project_id/collaborators | ||
def create | ||
mails_handler = MailsHandler.new(params[:emails], @project, @current_user) | ||
# parse mails as valid or invalid | ||
mails_handler.parse | ||
|
||
render json: { | ||
added: mails_handler.added_mails, | ||
existing: mails_handler.existing_mails, | ||
invalid: mails_handler.invalid_mails | ||
} | ||
end | ||
|
||
# DELETE /api/v1//projects/:project_id/collaborators/:id | ||
# :id is essentially the user_id for the user to be removed from project | ||
def destroy | ||
@collaboration = Collaboration.find_by(user: @collaborator, project: @project) | ||
@collaboration.destroy! | ||
render json: {}, status: :no_content | ||
end | ||
|
||
private | ||
|
||
def set_project | ||
@project = Project.find(params[:project_id]) | ||
end | ||
|
||
def check_author_access | ||
authorize @project, :author_access? | ||
end | ||
|
||
def check_view_access | ||
authorize @project, :check_view_access? | ||
end | ||
|
||
def set_collaborator | ||
@collaborator = @project.collaborators.find(params[:id]) | ||
end | ||
|
||
def collaborator_params | ||
params.require(:collaborator).permit(:project_id, :emails) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
app/services/api/v1/collaborators_controller/mails_handler.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# frozen_string_literal: true | ||
|
||
class Api::V1::CollaboratorsController | ||
class MailsHandler | ||
attr_reader :valid_mails, :invalid_mails, :existing_mails | ||
|
||
# initialize the class with mails, project and current_user to be used in class | ||
def initialize(mails, project, current_user) | ||
@mails = mails | ||
@project = project | ||
@current_user = current_user | ||
# initialize empty valid and invalid mails | ||
@valid_mails = [] | ||
@invalid_mails = [] | ||
end | ||
|
||
# parse emails as valid, invalid or existing mails | ||
def parse | ||
@mails.split(",").each do |email| | ||
email = email.strip | ||
if email.present? && email != @current_user.email && Devise.email_regexp.match?(email) | ||
@valid_mails.push(email) | ||
else | ||
@invalid_mails.push(email) | ||
end | ||
end | ||
|
||
@existing_mails = User.where( | ||
id: @project.collaborations.pluck(:user_id) | ||
).pluck(:email) | ||
end | ||
|
||
def added_mails | ||
newly_added = valid_mails - existing_mails | ||
added_mails = [] | ||
# checks if user exists and adds to added_mails | ||
newly_added.each do |email| | ||
user = User.find_by(email: email) | ||
if user.present? | ||
added_mails.push(email) | ||
Collaboration.where(project_id: @project.id, user_id: user.id).first_or_create | ||
end | ||
end | ||
# returns added_mails | ||
added_mails | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
spec/requests/api/v1/collaborators_controller/create_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# frozen_string_literal: true | ||
|
||
require "rails_helper" | ||
|
||
RSpec.describe Api::V1::CollaboratorsController, "#create", type: :request do | ||
describe "create/add collaborators" do | ||
let!(:author) { FactoryBot.create(:user) } | ||
let!(:project) { FactoryBot.create(:project, author: author) } | ||
let!(:user) { FactoryBot.create(:user) } | ||
|
||
context "when not authenticated" do | ||
before do | ||
post "/api/v1/projects/#{project.id}/collaborators/", as: :json | ||
end | ||
|
||
it "returns status unauthenticated" do | ||
expect(response).to have_http_status(401) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authenticated as random user and don't have author_access?" do | ||
before do | ||
token = get_auth_token(FactoryBot.create(:user)) | ||
post "/api/v1/projects/#{project.id}/collaborators/", | ||
headers: { "Authorization": "Token #{token}" }, | ||
params: create_params, as: :json | ||
end | ||
|
||
it "returns status unauthorized" do | ||
expect(response).to have_http_status(403) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authorized but tries to add collaborator to non existent project" do | ||
before do | ||
token = get_auth_token(author) | ||
post "/api/v1/projects/0/collaborators/", | ||
headers: { "Authorization": "Token #{token}" }, | ||
params: create_params, as: :json | ||
end | ||
|
||
it "returns status not_found" do | ||
expect(response).to have_http_status(404) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authorized and has access to add collaborator" do | ||
before do | ||
# creates a collaboration | ||
existing = FactoryBot.create(:user, email: "existing@test.com") | ||
FactoryBot.create(:collaboration, user: existing, project: project) | ||
token = get_auth_token(author) | ||
post "/api/v1/projects/#{project.id}/collaborators/", | ||
headers: { "Authorization": "Token #{token}" }, | ||
params: create_params, as: :json | ||
end | ||
|
||
it "returns status code 200" do | ||
expect(response).to have_http_status(200) | ||
end | ||
|
||
it "returns the added, already_existing & invalid mails (author being invalid)" do | ||
expect(response.parsed_body["added"]).to eq([user.email]) | ||
puts user.email | ||
expect(response.parsed_body["existing"]).to eq(["existing@test.com"]) | ||
expect(response.parsed_body["invalid"]).to eq(["invalid", author.email]) | ||
end | ||
end | ||
|
||
def create_params | ||
{ | ||
"emails": "#{user.email}, existing@test.com, invalid, #{author.email}" | ||
} | ||
end | ||
end | ||
end |
64 changes: 64 additions & 0 deletions
64
spec/requests/api/v1/collaborators_controller/destroy_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# frozen_string_literal: true | ||
|
||
require "rails_helper" | ||
|
||
RSpec.describe Api::V1::CollaboratorsController, "#destroy", type: :request do | ||
describe "delete specific collaborator" do | ||
let!(:author) { FactoryBot.create(:user) } | ||
let!(:project) { FactoryBot.create(:project, author: author) } | ||
let!(:collaborator) { FactoryBot.create(:user) } | ||
let!(:collaboration) { FactoryBot.create(:collaboration, user: collaborator, project: project) } | ||
|
||
context "when not authenticated" do | ||
before do | ||
delete "/api/v1/projects/#{project.id}/collaborators/#{collaboration.user.id}", as: :json | ||
end | ||
|
||
it "returns status unauthenticated" do | ||
expect(response).to have_http_status(401) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authenticated as random user and don't have author_access?" do | ||
before do | ||
token = get_auth_token(FactoryBot.create(:user)) | ||
delete "/api/v1/projects/#{project.id}/collaborators/#{collaboration.user.id}", | ||
headers: { "Authorization": "Token #{token}" }, as: :json | ||
end | ||
|
||
it "returns status unauthorized" do | ||
expect(response).to have_http_status(403) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authenticated but tries to delete non existent collaborator" do | ||
before do | ||
token = get_auth_token(author) | ||
delete "/api/v1/projects/#{project.id}/collaborators/0", | ||
headers: { "Authorization": "Token #{token}" }, as: :json | ||
end | ||
|
||
it "returns status not_found" do | ||
expect(response).to have_http_status(404) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authenticated and has access to delete collaborator" do | ||
before do | ||
token = get_auth_token(author) | ||
delete "/api/v1/projects/#{project.id}/collaborators/#{collaboration.user.id}", | ||
headers: { "Authorization": "Token #{token}" }, as: :json | ||
end | ||
|
||
it "deletes collaborator & return status no_content" do | ||
expect { Collaboration.find_by!(user: collaborator, project: project) }.to raise_exception( | ||
ActiveRecord::RecordNotFound | ||
) | ||
expect(response).to have_http_status(204) | ||
end | ||
end | ||
end | ||
end |
72 changes: 72 additions & 0 deletions
72
spec/requests/api/v1/collaborators_controller/index_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# frozen_string_literal: true | ||
|
||
require "rails_helper" | ||
|
||
RSpec.describe Api::V1::CollaboratorsController, "#index", type: :request do | ||
describe "list all collaborators" do | ||
let!(:author) { FactoryBot.create(:user) } | ||
let!(:public_project) do | ||
FactoryBot.create(:project, author: author, project_access_type: "Public") | ||
end | ||
let!(:private_project) { FactoryBot.create(:project, author: author) } | ||
|
||
context "when not authenticated" do | ||
before do | ||
get "/api/v1/projects/#{public_project.id}/collaborators/", as: :json | ||
end | ||
|
||
it "returns status unauthorized" do | ||
expect(response).to have_http_status(401) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authorized but invalid/non-existent project" do | ||
before do | ||
token = get_auth_token(FactoryBot.create(:user)) | ||
get "/api/v1/projects/0/collaborators/", | ||
headers: { "Authorization": "Token #{token}" }, as: :json | ||
end | ||
|
||
it "returns status not_found" do | ||
expect(response).to have_http_status(404) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
|
||
context "when authorized to fetch project's collaborators which user has view access to" do | ||
before do | ||
# create 3 collaborators for a public project | ||
FactoryBot.create_list(:user, 3).each do |u| | ||
FactoryBot.create(:collaboration, user: u, project: public_project) | ||
end | ||
token = get_auth_token(FactoryBot.create(:user)) | ||
get "/api/v1/projects/#{public_project.id}/collaborators/", | ||
headers: { "Authorization": "Token #{token}" }, as: :json | ||
end | ||
|
||
it "returns all the collaborators for the given project" do | ||
expect(response).to have_http_status(200) | ||
expect(response).to match_response_schema("users") | ||
expect(response.parsed_body["data"].length).to eq(3) | ||
end | ||
end | ||
|
||
context "when fetching project's collaborators which user doesn't have view access to" do | ||
before do | ||
# create 3 collaborators for a private project | ||
FactoryBot.create_list(:user, 3).each do |u| | ||
FactoryBot.create(:collaboration, user: u, project: private_project) | ||
end | ||
token = get_auth_token(FactoryBot.create(:user)) | ||
get "/api/v1/projects/#{private_project.id}/collaborators/", | ||
headers: { "Authorization": "Token #{token}" }, as: :json | ||
end | ||
|
||
it "returns status unauthorized" do | ||
expect(response).to have_http_status(403) | ||
expect(response.parsed_body).to have_jsonapi_errors | ||
end | ||
end | ||
end | ||
end |