From 0fbf4a86ec1e6a75631e4e510fba4495407630db Mon Sep 17 00:00:00 2001 From: Jasder <2053003901@@qq.com> Date: Fri, 10 Jan 2020 15:58:35 +0800 Subject: [PATCH] ADD project's members api --- app/controllers/application_controller.rb | 15 ++++- app/controllers/members_controller.rb | 59 +++++++++++++++++++ app/forms/projects/change_member_role_form.rb | 10 ++++ app/helpers/members_helper.rb | 2 + .../projects/add_member_interactor.rb | 42 +++++++++++++ .../projects/change_member_role_interactor.rb | 51 ++++++++++++++++ .../projects/delete_member_interactor.rb | 41 +++++++++++++ app/models/concerns/project_operable.rb | 43 ++++++++++++++ app/models/member.rb | 2 + app/models/member_role.rb | 2 + app/models/project.rb | 11 +--- app/models/user.rb | 8 +++ .../gitea/repository/members/add_service.rb | 28 +++++++++ .../repository/members/delete_service.rb | 25 ++++++++ .../projects/change_member_role_service.rb | 18 ++++++ app/views/members/_member.json.jbuilder | 4 ++ app/views/members/index.json.jbuilder | 6 ++ config/routes.rb | 7 +++ 18 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 app/controllers/members_controller.rb create mode 100644 app/forms/projects/change_member_role_form.rb create mode 100644 app/helpers/members_helper.rb create mode 100644 app/interactors/projects/add_member_interactor.rb create mode 100644 app/interactors/projects/change_member_role_interactor.rb create mode 100644 app/interactors/projects/delete_member_interactor.rb create mode 100644 app/models/concerns/project_operable.rb create mode 100644 app/services/gitea/repository/members/add_service.rb create mode 100644 app/services/gitea/repository/members/delete_service.rb create mode 100644 app/services/projects/change_member_role_service.rb create mode 100644 app/views/members/_member.json.jbuilder create mode 100644 app/views/members/index.json.jbuilder diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3c2b5afa7..2bf20ac4a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -632,13 +632,18 @@ class ApplicationController < ActionController::Base render_not_found("未找到’#{params[:login]}’相关的用户") unless @user end + def find_user_with_id + @user = User.find_by_id params[:user_id] + render_not_found("未找到’#{params[:login]}’相关的用户") unless @user + end + def find_repository @repo = @user.repositories.find_by_identifier params[:repo_identifier] render_not_found("未找到’#{params[:repo_identifier]}’相关的项目") unless @repo end def find_project - @project = Project.find_by_identifier! params[:id] + @project = Project.find_by_identifier!(params[:id]) || (Project.find params[:project_id]) render_not_found("未找到’#{params[:id]}’相关的项目") unless @project end @@ -649,6 +654,14 @@ class ApplicationController < ActionController::Base tip_exception(e.message) end + def render_response(interactor) + if interactor.success? + render_ok + else + render_error(interactor.error) + end + end + private def object_not_found uid_logger("Missing template or cant't find record, responding with 404") diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb new file mode 100644 index 000000000..281159259 --- /dev/null +++ b/app/controllers/members_controller.rb @@ -0,0 +1,59 @@ +class MembersController < ApplicationController + before_action :require_login + before_action :find_project_with_id + before_action :find_user_with_id, only: %i[create remove change_role] + before_action :operate!, except: %i[index] + before_action :check_member_exists!, only: %i[create] + before_action :check_member_not_exists!, only: %i[remove change_role] + + def create + interactor = Projects::AddMemberInteractor.call(current_user, @project, @user) + render_response(interactor) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def index + scope = @project.members.includes(:user, :roles) + @total_count = scope.size + @members = paginate(scope) + end + + def remove + interactor = Projects::DeleteMemberInteractor.call(current_user, @project, @user) + render_response(interactor) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def change_role + interactor = Projects::ChangeMemberRoleInteractor.call(current_user, @project, @user, params[:role]) + render_response(interactor) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + private + def can_operate? + current_user.project_manager?(@project) + end + + def member_exists? + @project.member?(params[:user_id]) + end + + def operate! + return render_forbidden('你不是管理员,没有权限操作') unless can_operate? + end + + def check_member_exists! + return render_result(1, "user_id为#{params[:user_id]}的用户已经是项目成员") if member_exists? + end + + def check_member_not_exists! + return render_result(1, "user_id为#{params[:user_id]}的用户还不是项目成员") unless member_exists? + end +end diff --git a/app/forms/projects/change_member_role_form.rb b/app/forms/projects/change_member_role_form.rb new file mode 100644 index 000000000..c64e85934 --- /dev/null +++ b/app/forms/projects/change_member_role_form.rb @@ -0,0 +1,10 @@ +class Projects::ChangeMemberRoleForm < BaseForm + attr_accessor :user_id, :role + + validates :user_id, :role, presence: true + validate :check_roles + + def check_roles + raise '无效的role值.' unless ["Manager","Developer", "Reporter"].include? role + end +end diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb new file mode 100644 index 000000000..e3fa623c7 --- /dev/null +++ b/app/helpers/members_helper.rb @@ -0,0 +1,2 @@ +module MembersHelper +end diff --git a/app/interactors/projects/add_member_interactor.rb b/app/interactors/projects/add_member_interactor.rb new file mode 100644 index 000000000..a3fe9e33e --- /dev/null +++ b/app/interactors/projects/add_member_interactor.rb @@ -0,0 +1,42 @@ +module Projects + class AddMemberInteractor + def self.call(owner, project, collaborator, permission="write") + interactor = new(owner, project, collaborator, permission) + interactor.run + interactor + end + + attr_reader :error, :result + + def initialize(owner, project, collaborator, permission) + @owner = owner + @project = project + @collaborator = collaborator + @permission = permission + end + + def success? + @error.nil? + end + + def run + ActiveRecord::Base.transaction do + gitea_result = Gitea::Repository::Members::AddService.new(owner, project.identifier, collaborator.login, permission).call + if gitea_result.status == 204 + project.add_member!(collaborator.id) + end + fail!(nil) + end + rescue Exception => exception + fail!(exception.message) + end + + private + attr_reader :owner, :project, :collaborator, :permission + + def fail!(error) + @error = error + end + + end +end diff --git a/app/interactors/projects/change_member_role_interactor.rb b/app/interactors/projects/change_member_role_interactor.rb new file mode 100644 index 000000000..6f1643995 --- /dev/null +++ b/app/interactors/projects/change_member_role_interactor.rb @@ -0,0 +1,51 @@ +module Projects + class ChangeMemberRoleInteractor + def self.call(owner, project, collaborator, role) + interactor = new(owner, project, collaborator, role) + interactor.run + interactor + end + + attr_reader :error, :result + + def initialize(owner, project, collaborator, role) + @owner = owner + @collaborator = collaborator + @project = project + @role = role + end + + def success? + @error.nil? + end + + def run + Projects::ChangeMemberRoleForm.new({user_id: collaborator.id, role: role}).validate! + ActiveRecord::Base.transaction do + gitea_result = Gitea::Repository::Members::AddService.new(owner, project.identifier, collaborator.login, treated_role).call + if gitea_result.status == 204 + Projects::ChangeMemberRoleService.new(project, collaborator.id, role).call + fail!(nil) + end + end + rescue Exception => exception + fail!(exception.message) + end + + private + attr_reader :role, :project, :collaborator, :owner + + def fail!(error) + @error = error + end + + def treated_role + case role + when "Manager" then "admin" + when "Developer" then "write" + when "Reporter" then "read" + end + end + + end +end diff --git a/app/interactors/projects/delete_member_interactor.rb b/app/interactors/projects/delete_member_interactor.rb new file mode 100644 index 000000000..ee99457e4 --- /dev/null +++ b/app/interactors/projects/delete_member_interactor.rb @@ -0,0 +1,41 @@ +module Projects + class DeleteMemberInteractor + def self.call(owner, project, collaborator) + interactor = new(owner, project, collaborator) + interactor.run + interactor + end + + attr_reader :error, :result + + def initialize(owner, project, collaborator) + @owner = owner + @project = project + @collaborator = collaborator + end + + def success? + @error.nil? + end + + def run + ActiveRecord::Base.transaction do + gitea_result = Gitea::Repository::Members::DeleteService.new(owner, project.identifier, collaborator.login).call + if gitea_result.status == 204 + project.remove_member!(collaborator.id) + end + fail!(nil) + end + rescue Exception => exception + fail!(exception.message) + end + + private + attr_reader :owner, :project, :collaborator + + def fail!(error) + @error = error + end + + end +end diff --git a/app/models/concerns/project_operable.rb b/app/models/concerns/project_operable.rb new file mode 100644 index 000000000..4307ae543 --- /dev/null +++ b/app/models/concerns/project_operable.rb @@ -0,0 +1,43 @@ +module ProjectOperable + extend ActiveSupport::Concern + + included do + has_many :members + # has_many :except_owner_members, -> { members.where("members.use_id != ? ", self.owner.id ) } + has_many :manager_members, -> { joins(:roles).where(roles: { name: 'Manager' }) }, class_name: 'Member' + end + + def add_member!(user_id, role_name='Developer') + member = members.create!(user_id: user_id) + set_developer_role(member) + end + + def remove_member!(user_id) + member = members.find_by(user_id: user_id) + member.destroy! if member && self.user_id != user_id + end + + def member?(user_id) + members.exists?(user_id: user_id) + end + + # 除了项目创建者本身 + def member(user_id) + members.where.not("members.user_id = ? ", owner.id).find_by(user_id: user_id) + end + + def change_member_role!(user_id, role) + member = self.member(user_id) + member.member_roles.last.update_attributes!(role: role) + end + + def owner?(user) + self.owner == user + end + + def set_developer_role(member) + role = Role.find_by_name 'Developer' + member.member_roles.create!(role: role) + end + +end diff --git a/app/models/member.rb b/app/models/member.rb index a9173cab1..132b96692 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -6,4 +6,6 @@ class Member < ApplicationRecord has_many :member_roles, dependent: :destroy has_many :roles, through: :member_roles + validates :user_id, :project_id, presence: true + end diff --git a/app/models/member_role.rb b/app/models/member_role.rb index 2461c52f1..6e51e7011 100644 --- a/app/models/member_role.rb +++ b/app/models/member_role.rb @@ -1,4 +1,6 @@ class MemberRole < ApplicationRecord belongs_to :role belongs_to :member + + validates :member_id, :role_id, presence: true end diff --git a/app/models/project.rb b/app/models/project.rb index 9141f0c8c..6bca5a891 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,6 +2,7 @@ class Project < ApplicationRecord include Matchable include Publicable include Watchable + include ProjectOperable enum project_type: { mirror: 1, common: 0 } # common:开源托管项目, mirror:开源镜像项目 @@ -14,8 +15,6 @@ class Project < ApplicationRecord has_many :commits - has_many :members - has_many :manager_members, -> { joins(:roles).where(roles: { name: 'Manager' }) }, class_name: 'Member' has_one :project_score, dependent: :destroy has_one :repository, dependent: :destroy has_many :issue_tags @@ -37,14 +36,6 @@ class Project < ApplicationRecord User.find(user_id).full_name end - def project_members - self.members - end - - def member?(user) - members.exists?(user_id: user.id) || user.id == self.user_id - end - def members_user_infos members.joins("left join users on members.user_id = users.id").includes(:user) # members.joins("left join users on members.user_id = users.id").select("users.id", "users.login","users.firstname","users.lastname") diff --git a/app/models/user.rb b/app/models/user.rb index 388445e7d..d870e9c90 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < ApplicationRecord include Watchable include Likeable include BaseModel + include ProjectOperable include Searchable::Dependents::User # Account statuses @@ -32,6 +33,9 @@ class User < ApplicationRecord LOGIN_CHARS = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z).freeze + # FIX Invalid single-table inheritance type + self.inheritance_column = nil + # educoder: 来自Educoder平台 # trustie: 来自Trustie平台 # forge: 平台本身注册的用户 @@ -216,6 +220,10 @@ class User < ApplicationRecord mail.blank? ? "#{login}@educoder.net" : mail end + def project_manager?(project) + project.manager_members.exists?(user: self) || self.admin? + end + # 学号 def student_id self.user_extension.try(:student_id) diff --git a/app/services/gitea/repository/members/add_service.rb b/app/services/gitea/repository/members/add_service.rb new file mode 100644 index 000000000..f6b831bbc --- /dev/null +++ b/app/services/gitea/repository/members/add_service.rb @@ -0,0 +1,28 @@ +# 添加协作者/或者更改协作这的可读写权限 +class Gitea::Repository::Members::AddService < Gitea::ClientService + attr_reader :owner, :repo_name, :collaborator, :permission + + # owner: owner of the repo + # repo_name: name of the repo + # collaborator: username of the collaborator + # permission: permission name, FIX: admin | read | write + def initialize(owner, repo_name, collaborator, permission) + @owner = owner + @repo_name = repo_name + @collaborator = collaborator + @permission = permission + end + + def call + put(url, params) + end + + private + def params + Hash.new.merge(token: owner.gitea_token, data: {permission: permission}) + end + + def url + "/repos/#{owner.login}/#{repo_name}/collaborators/#{collaborator}".freeze + end +end diff --git a/app/services/gitea/repository/members/delete_service.rb b/app/services/gitea/repository/members/delete_service.rb new file mode 100644 index 000000000..fa7b02e93 --- /dev/null +++ b/app/services/gitea/repository/members/delete_service.rb @@ -0,0 +1,25 @@ +class Gitea::Repository::Members::DeleteService < Gitea::ClientService + attr_reader :owner, :repo_name, :collaborator + + # owner: owner of the repo + # repo_name: name of the repo + # collaborator: username of the collaborator + def initialize(owner, repo_name, collaborator) + @owner = owner + @repo_name = repo_name + @collaborator = collaborator + end + + def call + delete(url, params) + end + + private + def params + Hash.new.merge(token: owner.gitea_token) + end + + def url + "/repos/#{owner.login}/#{repo_name}/collaborators/#{collaborator}".freeze + end +end diff --git a/app/services/projects/change_member_role_service.rb b/app/services/projects/change_member_role_service.rb new file mode 100644 index 000000000..6802c673e --- /dev/null +++ b/app/services/projects/change_member_role_service.rb @@ -0,0 +1,18 @@ +class Projects::ChangeMemberRoleService < ApplicationService + attr_reader :project, :user_id, :role + + def initialize(project, user_id, role) + @project = project + @user_id = user_id + @role = role + end + + def call + ActiveRecord::Base.transaction do + tmp_role = Role.find_by_name role + @project.change_member_role!(user_id, tmp_role) + end + rescue => e + raise Error, e.message + end +end diff --git a/app/views/members/_member.json.jbuilder b/app/views/members/_member.json.jbuilder new file mode 100644 index 000000000..280f7ef98 --- /dev/null +++ b/app/views/members/_member.json.jbuilder @@ -0,0 +1,4 @@ +json.id user.id +json.name user.real_name +json.login user.login +json.image_url url_to_avatar(user) diff --git a/app/views/members/index.json.jbuilder b/app/views/members/index.json.jbuilder new file mode 100644 index 000000000..dbffc6f38 --- /dev/null +++ b/app/views/members/index.json.jbuilder @@ -0,0 +1,6 @@ +json.total_count @total_count +json.members @members do |member| + json.partial! 'member', user: member.user + json.is_owner @project.owner?(member.user) + json.role member.roles.last.name +end diff --git a/config/routes.rb b/config/routes.rb index 8ac59006b..0219de512 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,6 +74,13 @@ Rails.application.routes.draw do end end + resources :members, only: [:index, :create] do + collection do + delete :remove + put :change_role + end + end + collection do post :migrate get :group_type_list