diff --git a/CHANGELOG.md b/CHANGELOG.md index 42422e3b..40165507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [v3.0.3](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-05-08 + +* BUGFIXES + * Fix 解决易修标题过长导致的排版问题(45469) + * Fix 解决合并请求详情页面排版错误的问题(45457) + * FIX 解决转移仓库界面专有名词描述错误的问题(45455) + * Fix 解决markdown格式文件自动生成数字排序的问题(45454) + * Fix 解决镜像项目源地址不显示的问题(45403) + * Fix 解决镜像项目导航显示错误问题(45398) + * Fix 解决其他相关bug + +* ENHANCEMENTS + * UPDATE 用户注册时,账号和密码正则匹配调整(45336) (45318) (45290) + * ADD 创建组织各属性添加规则匹配功能(45313) (45289) + * ADD 创建团建各属性添加规则匹配功能(45334) (45325) (45287) + * ADD 仓库转移功能(45017) (45015) + ## [v3.0.2](https://forgeplus.trustie.net/projects/jasder/forgeplus/releases) - 2021-04-23 * BUGFIXES diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 616927db..ac894c5f 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -176,6 +176,7 @@ class AccountsController < ApplicationController # 用户登录 def login + Users::LoginForm.new(account_params).validate! @user = User.try_to_login(params[:login], params[:password]) return normal_status(-2, "错误的账号或密码") if @user.blank? @@ -352,4 +353,7 @@ class AccountsController < ApplicationController params.require(:user).permit(:login, :email, :phone) end + def account_params + params.require(:account).permit(:login, :password) + end end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/controllers/concerns/acceleratorable.rb b/app/controllers/concerns/acceleratorable.rb new file mode 100644 index 00000000..9a11e46c --- /dev/null +++ b/app/controllers/concerns/acceleratorable.rb @@ -0,0 +1,28 @@ +module Acceleratorable + extend ActiveSupport::Concern + + def enable_accelerator?(clone_addr) + clone_addr.include?(github_domain) || clone_addr.include?(gitlab_domain) + end + + def accelerator_url(repo_name) + [accelerator_domain, accelerator_username, "#{repo_name}.git"].join('/') + end + + def github_domain + 'github.com' + end + + def gitlab_domain + 'gitlab.com' + end + + def accelerator_domain + Gitea.gitea_config[:accelerator]["domain"] + end + + def accelerator_username + Gitea.gitea_config[:accelerator]["access_key_id"] + end + +end \ No newline at end of file diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index a3b5fe92..3584d6bf 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -101,51 +101,46 @@ class IssuesController < ApplicationController end def create - if params[:subject].blank? - normal_status(-1, "标题不能为空") - elsif params[:subject].to_s.size > 255 - normal_status(-1, "标题不能超过255个字符") - else - issue_params = issue_send_params(params) - - @issue = Issue.new(issue_params) - if @issue.save! - if params[:attachment_ids].present? - params[:attachment_ids].each do |id| - attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) - unless attachment.blank? - attachment.container = @issue - attachment.author_id = current_user.id - attachment.description = "" - attachment.save - end + issue_params = issue_send_params(params) + Issues::CreateForm.new({subject:issue_params[:subject]}).validate! + @issue = Issue.new(issue_params) + if @issue.save! + if params[:attachment_ids].present? + params[:attachment_ids].each do |id| + attachment = Attachment.select(:id, :container_id, :container_type)&.find_by_id(id) + unless attachment.blank? + attachment.container = @issue + attachment.author_id = current_user.id + attachment.description = "" + attachment.save end end - if params[:issue_tag_ids].present? - params[:issue_tag_ids].each do |tag| - IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) - end + end + if params[:issue_tag_ids].present? + params[:issue_tag_ids].each do |tag| + IssueTagsRelate.create!(issue_id: @issue.id, issue_tag_id: tag) end - if params[:assigned_to_id].present? - Tiding.create!(user_id: params[:assigned_to_id], trigger_user_id: current_user.id, - container_id: @issue.id, container_type: 'Issue', - parent_container_id: @project.id, parent_container_type: "Project", - tiding_type: 'issue', status: 0) - end - - #为悬赏任务时, 扣除当前用户的积分 - if params[:issue_type].to_s == "2" - post_to_chain("minus", params[:token].to_i, current_user.try(:login)) - end - - @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") - render json: {status: 0, message: "创建成", id: @issue.id} - else - normal_status(-1, "创建失败") + end + if params[:assigned_to_id].present? + Tiding.create!(user_id: params[:assigned_to_id], trigger_user_id: current_user.id, + container_id: @issue.id, container_type: 'Issue', + parent_container_id: @project.id, parent_container_type: "Project", + tiding_type: 'issue', status: 0) end - end + #为悬赏任务时, 扣除当前用户的积分 + if params[:issue_type].to_s == "2" + post_to_chain("minus", params[:token].to_i, current_user.try(:login)) + end + @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") + render json: {status: 0, message: "创建成", id: @issue.id} + else + normal_status(-1, "创建失败") + end + rescue Exception => exception + puts exception.message + normal_status(-1, exception.message) end def edit @@ -199,7 +194,7 @@ class IssuesController < ApplicationController normal_status(-1, "不允许修改为关闭状态") else issue_params = issue_send_params(params).except(:issue_classify, :author_id, :project_id) - + Issues::UpdateForm.new({subject:issue_params[:subject]}).validate! if @issue.update_attributes(issue_params) if params[:status_id].to_i == 5 #任务由非关闭状态到关闭状态时 @issue.issue_times.update_all(end_time: Time.now) @@ -225,6 +220,9 @@ class IssuesController < ApplicationController normal_status(-1, "更新失败") end end + rescue Exception => exception + puts exception.message + normal_status(-1, exception.message) end def show diff --git a/app/controllers/organizations/base_controller.rb b/app/controllers/organizations/base_controller.rb index e6163bf8..e18ab9ae 100644 --- a/app/controllers/organizations/base_controller.rb +++ b/app/controllers/organizations/base_controller.rb @@ -17,6 +17,7 @@ class Organizations::BaseController < ApplicationController end def org_privacy_condition + return false if current_user.admin? @organization.organization_extension.privacy? && @organization.organization_users.where(user_id: current_user.id).blank? end diff --git a/app/controllers/organizations/organizations_controller.rb b/app/controllers/organizations/organizations_controller.rb index 09c12fd5..c8f4f4aa 100644 --- a/app/controllers/organizations/organizations_controller.rb +++ b/app/controllers/organizations/organizations_controller.rb @@ -36,6 +36,7 @@ class Organizations::OrganizationsController < Organizations::BaseController def update ActiveRecord::Base.transaction do + Organizations::CreateForm.new(organization_params).validate! login = @organization.login @organization.login = organization_params[:name] if organization_params[:name].present? @organization.nickname = organization_params[:nickname] if organization_params[:nickname].present? diff --git a/app/controllers/organizations/teams_controller.rb b/app/controllers/organizations/teams_controller.rb index a8d06ce4..c599ac42 100644 --- a/app/controllers/organizations/teams_controller.rb +++ b/app/controllers/organizations/teams_controller.rb @@ -43,6 +43,7 @@ class Organizations::TeamsController < Organizations::BaseController end def update + Organizations::CreateTeamForm.new(team_params).validate! @team = Organizations::Teams::UpdateService.call(current_user, @team, team_params) rescue Exception => e uid_logger_error(e.message) diff --git a/app/controllers/projects/applied_transfer_projects_controller.rb b/app/controllers/projects/applied_transfer_projects_controller.rb new file mode 100644 index 00000000..e62033f2 --- /dev/null +++ b/app/controllers/projects/applied_transfer_projects_controller.rb @@ -0,0 +1,26 @@ +class Projects::AppliedTransferProjectsController < Projects::BaseController + before_action :check_auth + + def organizations + @organizations = Organization.includes(:organization_extension).joins(team_users: :team).where(team_users: {user_id: current_user.id}, teams: {authorize: %w(admin owner)}) + end + + def create + @applied_transfer_project = Projects::ApplyTransferService.call(current_user, @project, params) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def cancel + @applied_transfer_project = Projects::CancelTransferService.call(current_user, @project) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + private + def check_auth + return render_forbidden unless current_user.admin? ||@project.owner?(current_user) + end +end \ No newline at end of file diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ca66b436..500c584c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -2,6 +2,8 @@ class ProjectsController < ApplicationController include ApplicationHelper include OperateProjectAbilityAble include ProjectsHelper + include Acceleratorable + before_action :require_login, except: %i[index branches group_type_list simple show fork_users praise_users watch_users recommend about menu_list] before_action :load_project, except: %i[index group_type_list migrate create recommend] before_action :authorizate_user_can_edit_project!, only: %i[update] @@ -53,7 +55,23 @@ class ProjectsController < ApplicationController def migrate Projects::MigrateForm.new(mirror_params).validate! - @project = Projects::MigrateService.new(current_user, mirror_params).call + + @project = + if enable_accelerator?(mirror_params[:clone_addr]) + source_clone_url = mirror_params[:clone_addr] + uid_logger("########## 已动加速器 ##########") + result = Gitea::Accelerator::MigrateService.call(mirror_params) + if result[:status] == :success + Rails.logger.info "########## 加速镜像成功 ########## " + Projects::MigrateService.call(current_user, + mirror_params.merge(source_clone_url: source_clone_url, + clone_addr: accelerator_url(mirror_params[:repository_name]))) + else + Projects::MigrateService.call(current_user, mirror_params) + end + else + Projects::MigrateService.call(current_user, mirror_params) + end rescue Exception => e uid_logger_error(e.message) tip_exception(e.message) @@ -88,7 +106,7 @@ class ProjectsController < ApplicationController def update ActiveRecord::Base.transaction do - # Projects::CreateForm.new(project_params).validate! + Projects::UpdateForm.new(project_params).validate! private = params[:private] || false new_project_params = project_params.except(:private).merge(is_public: !private) @@ -149,7 +167,7 @@ class ProjectsController < ApplicationController end def recommend - @projects = Project.recommend.includes(:repository, :project_category, :owner).limit(5) + @projects = Project.recommend.includes(:repository, :project_category, :owner).order(id: :desc).limit(5) end def about diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index c59a196b..0dbcdc94 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -140,7 +140,7 @@ class PullRequestsController < ApplicationController end def pr_merge - return render_forbidden("你没有权限操作.") unless current_user.project_manager?(@project) + return render_forbidden("你没有权限操作.") unless @project.operator?(current_user) if params[:do].blank? normal_status(-1, "请选择合并方式") @@ -149,12 +149,12 @@ class PullRequestsController < ApplicationController begin result = PullRequests::MergeService.call(@owner, @repository, @pull_request, current_user, params) - if result && @pull_request.merge! + if result.status == 200 && @pull_request.merge! @pull_request.project_trend_status! @issue&.custom_journal_detail("merge", "", "该合并请求已被合并", current_user&.id) normal_status(1, "合并成功") else - normal_status(-1, "合并失败") + normal_status(-1, result.message) end rescue => e normal_status(-1, e.message) @@ -215,7 +215,7 @@ class PullRequestsController < ApplicationController def get_relatived @project_tags = @project.issue_tags&.select(:id,:name, :color).as_json @project_versions = @project.versions&.select(:id,:name, :status).as_json - @project_members = @project.members_user_infos + @project_members = @project.all_developers @project_priories = IssuePriority&.select(:id,:name, :position).as_json end diff --git a/app/controllers/users/applied_messages_controller.rb b/app/controllers/users/applied_messages_controller.rb new file mode 100644 index 00000000..e80cabf9 --- /dev/null +++ b/app/controllers/users/applied_messages_controller.rb @@ -0,0 +1,18 @@ +class Users::AppliedMessagesController < Users::BaseController + before_action :check_auth + after_action :view_messages, only: [:index] + + def index + @applied_messages = @_observed_user.applied_messages.order(viewed: :asc, created_at: :desc) + @applied_messages = paginate @applied_messages + end + + private + def check_auth + return render_forbidden unless observed_logged_user? + end + + def view_messages + @applied_messages.update_all(viewed: 'viewed') + end +end \ No newline at end of file diff --git a/app/controllers/users/applied_transfer_projects_controller.rb b/app/controllers/users/applied_transfer_projects_controller.rb new file mode 100644 index 00000000..b1777f52 --- /dev/null +++ b/app/controllers/users/applied_transfer_projects_controller.rb @@ -0,0 +1,41 @@ +class Users::AppliedTransferProjectsController < Users::BaseController + before_action :check_auth + before_action :find_applied_transfer_project, except: [:index] + before_action :find_project, except: [:index] + + def index + user_collection_sql = AppliedTransferProject.where(owner_id: @_observed_user.id).to_sql + org_collection_sql = AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @_observed_user.id}, teams: {authorize: %w(admin owner)} )).to_sql + @applied_transfer_projects = AppliedTransferProject.from("( #{ user_collection_sql } UNION #{ org_collection_sql } ) AS applied_transfer_projects") + @applied_transfer_projects = paginate @applied_transfer_projects.order("created_at desc") + end + + # 接受迁移 + def accept + @applied_transfer_project = Projects::AcceptTransferService.call(current_user, @project) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + # 拒绝迁移 + def refuse + @applied_transfer_project = Projects::RefuseTransferService.call(current_user, @project) + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + private + def check_auth + return render_forbidden unless observed_logged_user? + end + + def find_applied_transfer_project + @applied_transfer_project = AppliedTransferProject.find_by_id params[:id] + end + + def find_project + @project = @applied_transfer_project.project + end +end \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7f49ab61..3c62b114 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -27,12 +27,24 @@ class UsersController < ApplicationController def show #待办事项,现在未做 - @undo_events = 0 + if User.current.login == @user.login + @waiting_applied_messages = @user.applied_messages.waiting + @common_applied_transfer_projects = AppliedTransferProject.where(owner_id: @user.id).common + AppliedTransferProject.where(owner_id: Organization.joins(team_users: :team).where(team_users: {user_id: @user.id}, teams: {authorize: %w(admin owner)} )).common + @undo_events = @waiting_applied_messages.size + @common_applied_transfer_projects.size + else + @waiting_applied_messages = AppliedMessage.none + @common_applied_transfer_projects = AppliedTransferProject.none + @undo_events = 0 + end #用户的组织数量 # @user_composes_count = @user.composes.size @user_composes_count = 0 - @user_org_count = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)).size + @user.organizations.with_visibility("privacy").joins(:organization_users).where(organization_users: {user_id: current_user.id}).size : @user.organizations.with_visibility("common").size - user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? @user.projects : @user.projects.visible + user_organizations = User.current.logged? ? @user.organizations.with_visibility(%w(common limited)) + @user.organizations.with_visibility("privacy").joins(:team_users).where(team_users: {user_id: current_user.id}) : @user.organizations.with_visibility("common") + @user_org_count = user_organizations.size + normal_projects = Project.members_projects(@user.id).to_sql + org_projects = Project.joins(team_projects: [team: :team_users]).where(team_users: {user_id: @user.id}).to_sql + projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct + user_projects = User.current.logged? && (User.current.admin? || User.current.login == @user.login) ? projects : projects.visible @projects_common_count = user_projects.common.size @projects_mirrior_count = user_projects.mirror.size @projects_sync_mirrior_count = user_projects.sync_mirror.size diff --git a/app/forms/issues/create_form.rb b/app/forms/issues/create_form.rb new file mode 100644 index 00000000..7dde8ecd --- /dev/null +++ b/app/forms/issues/create_form.rb @@ -0,0 +1,11 @@ +class Issues::CreateForm + include ActiveModel::Model + + attr_accessor :subject + + validates :subject, presence: { message: "不能为空" } + + validates :subject, length: { maximum: 80, too_long: "不能超过80个字符" } + + +end diff --git a/app/forms/issues/update_form.rb b/app/forms/issues/update_form.rb new file mode 100644 index 00000000..7447c8cc --- /dev/null +++ b/app/forms/issues/update_form.rb @@ -0,0 +1,10 @@ +class Issues::UpdateForm + include ActiveModel::Model + + attr_accessor :subject + + validates :subject, presence: { message: "不能为空" } + + validates :subject, length: { maximum: 80, too_long: "不能超过80个字符" } + +end \ No newline at end of file diff --git a/app/forms/organizations/create_form.rb b/app/forms/organizations/create_form.rb index 00c1dd15..d92a9904 100644 --- a/app/forms/organizations/create_form.rb +++ b/app/forms/organizations/create_form.rb @@ -3,6 +3,9 @@ class Organizations::CreateForm < BaseForm attr_accessor :name, :description, :website, :location, :repo_admin_change_team_access, :visibility, :max_repo_creation, :nickname validates :name, :nickname, :visibility, presence: true + validates :name, :nickname, length: { maximum: 100 } + validates :location, length: { maximum: 50 } + validates :description, length: { maximum: 200 } validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } end diff --git a/app/forms/organizations/create_team_form.rb b/app/forms/organizations/create_team_form.rb index 898d69a1..6d816b0e 100644 --- a/app/forms/organizations/create_team_form.rb +++ b/app/forms/organizations/create_team_form.rb @@ -2,7 +2,9 @@ class Organizations::CreateTeamForm < BaseForm NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 attr_accessor :name, :nickname, :description, :authorize, :includes_all_project, :can_create_org_project, :unit_types - validates :name, :nickname, :authorize, presence: true + validates :name, :nickname, presence: true + validates :name, :nickname, length: { maximum: 100 } + validates :description, length: { maximum: 200 } validates :name, format: { with: NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } end diff --git a/app/forms/projects/create_form.rb b/app/forms/projects/create_form.rb index 2838996b..0b57f215 100644 --- a/app/forms/projects/create_form.rb +++ b/app/forms/projects/create_form.rb @@ -2,11 +2,15 @@ class Projects::CreateForm < BaseForm REPOSITORY_NAME_REGEX = /^(?!_)(?!.*?_$)[a-zA-Z0-9_-]+$/ #只含有数字、字母、下划线不能以下划线开头和结尾 attr_accessor :user_id, :name, :description, :repository_name, :project_category_id, :project_language_id, :ignore_id, :license_id, :private, :owner - + validates :user_id, :name, :description,:repository_name, :project_category_id, :project_language_id, presence: true validates :repository_name, format: { with: REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } + validates :name, length: { maximum: 50 } + validates :repository_name, length: { maximum: 100 } + validates :description, length: { maximum: 200 } + validate :check_ignore, :check_license, :check_owner, :check_max_repo_creation validate do check_project_category(project_category_id) diff --git a/app/forms/projects/update_form.rb b/app/forms/projects/update_form.rb index 4696beaa..65810a82 100644 --- a/app/forms/projects/update_form.rb +++ b/app/forms/projects/update_form.rb @@ -1,4 +1,11 @@ class Projects::UpdateForm < BaseForm - attr_reader :name, :description, :repository_name, :project_category_id + attr_accessor :name, :description, :project_category_id, :project_language_id, :private + validates :name, :description, :project_category_id, :project_language_id, presence: true + validates :name, length: { maximum: 50 } + validates :description, length: { maximum: 200 } + validate do + check_project_category(project_category_id) + check_project_language(project_language_id) + end end diff --git a/app/forms/users/login_form.rb b/app/forms/users/login_form.rb new file mode 100644 index 00000000..2634a36c --- /dev/null +++ b/app/forms/users/login_form.rb @@ -0,0 +1,8 @@ +class Users::LoginForm + include ActiveModel::Model + + attr_accessor :password, :login + + validates :login, presence: true + validates :password, presence: true, length: { minimum: 8, maximum: 16 }, format: { with: CustomRegexp::PASSWORD, message: "8~16位,支持字母数字和符号" } +end \ No newline at end of file diff --git a/app/helpers/admins/projects_helper.rb b/app/helpers/admins/projects_helper.rb new file mode 100644 index 00000000..c6d94c4c --- /dev/null +++ b/app/helpers/admins/projects_helper.rb @@ -0,0 +1,14 @@ +module Admins::ProjectsHelper + + def link_to_project(project) + owner = project.owner + + if owner.is_a?(User) + link_to(project.owner&.real_name, "/users/#{project&.owner&.login}", target: '_blank') + elsif owner.is_a?(Organization) + link_to(project.owner&.real_name, "/organize/#{project&.owner&.login}", target: '_blank') + else + "" + end + end +end \ No newline at end of file diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index aec4b38d..226096c2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -34,15 +34,14 @@ module ProjectsHelper end def json_response(project, user) - # repo = project.repository - repo = Repository.includes(:mirror).select(:id, :mirror_url).find_by(project: project) + repo = Repository.includes(:mirror).select(:id, :mirror_url, :source_clone_url).find_by(project: project) tmp_json = {} unless project.common? tmp_json = tmp_json.merge({ mirror_status: repo.mirror_status, mirror_num: repo.mirror_num, - mirror_url: repo.mirror_url, + mirror_url: repo.remote_mirror_url, first_sync: repo.first_sync? }) end diff --git a/app/helpers/tag_chosen_helper.rb b/app/helpers/tag_chosen_helper.rb index afd51170..1e7879d1 100644 --- a/app/helpers/tag_chosen_helper.rb +++ b/app/helpers/tag_chosen_helper.rb @@ -124,14 +124,13 @@ module TagChosenHelper end def render_cache_collaborators(project) - cache_key = "all_collaborators/#{project.members.maximum('created_on')}" - + cache_key = "all_collaborators/#{project.all_collaborators.maximum('created_on')}" Rails.cache.fetch(cache_key) do - project.members.includes(:user).collect do |event| + project.all_collaborators.order(created_on: :desc).collect do |user| { - id: event.user&.id, - name: event.user&.show_real_name, - avatar_url: url_to_avatar(event.user), + id: user&.id, + name: user&.show_real_name, + avatar_url: url_to_avatar(user), is_chosen: '0' } end @@ -171,10 +170,8 @@ module TagChosenHelper # depended_issues_id = @depended_issues_id end - project_members = project.members_user_infos project_members_info = [] #指派给 - project_members.includes(user: :user_extension).each do |member| - user = member&.user + project.all_collaborators.includes(:user_extension).each do |user| if user real_name = user.try(:show_real_name) user_id = user.id diff --git a/app/jobs/send_transfer_project_applied_message_job.rb b/app/jobs/send_transfer_project_applied_message_job.rb new file mode 100644 index 00000000..92ea306d --- /dev/null +++ b/app/jobs/send_transfer_project_applied_message_job.rb @@ -0,0 +1,47 @@ +class SendTransferProjectAppliedMessageJob < ApplicationJob + queue_as :default + + def perform(applied_transfer_project, applied_user, message_status) + project = applied_transfer_project.project + owner = project.owner + return unless project.present? + return unless owner.present? + if owner.is_a?(Organization) + receivers = project.managers + owner.team_users.joins(:team).where(teams: {authorize: %w(owner admin)}) + else + receivers = project.managers + end + receivers.each do |rec| + next if applied_user.id == rec.user_id # 自己不要给自己发通知 + AppliedMessage.create!(user_id: rec.user_id, + applied: applied_transfer_project, + status: message_status, + name: build_name(project.name, applied_transfer_project&.owner&.real_name, message_status, applied_user&.real_name), + applied_user_id: applied_user.id, + project_id: project.id) + end + if message_status == 'successed' # 如果转移成功,给转移发起者发通知已转移成功 + AppliedMessage.find_or_create_by!(user_id: applied_transfer_project.user_id, + applied: applied_transfer_project, + status: message_status, + name: build_name(project.name, applied_transfer_project&.owner&.real_name, message_status), + applied_user_id: applied_user.id, + project_id: project.id) + end + end + + private + def build_name(repo_name, owner_name, message_status, applied_name="") + case message_status + when 'canceled' + return "取消转移【#{repo_name}】仓库" + when 'common' + return "正在将【#{repo_name}】仓库转移给【#{owner_name}】" + when 'successed' + return "【#{repo_name}】仓库成功转移给【#{owner_name}】" + when 'failure' + return "拒绝转移【#{repo_name}】仓库" + end + "" + end +end \ No newline at end of file diff --git a/app/models/applied_message.rb b/app/models/applied_message.rb index 8098e6e0..b3ebad34 100644 --- a/app/models/applied_message.rb +++ b/app/models/applied_message.rb @@ -19,5 +19,10 @@ class AppliedMessage < ApplicationRecord belongs_to :user belongs_to :applied, polymorphic: true + belongs_to :project + belongs_to :applied_user, class_name: 'User' + + enum viewed: {waiting: 0, viewed: 1} + enum status: {canceled: -1, common: 0, successed: 1, failure: 2} # -1 已取消 0 正在操作 1 操作成功 2 操作失败 end diff --git a/app/models/applied_transfer_project.rb b/app/models/applied_transfer_project.rb new file mode 100644 index 00000000..d0e0e5c4 --- /dev/null +++ b/app/models/applied_transfer_project.rb @@ -0,0 +1,28 @@ +# == Schema Information +# +# Table name: applied_transfer_projects +# +# id :integer not null, primary key +# project_id :integer +# owner_id :integer +# user_id :integer +# status :integer default("0") +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_applied_transfer_projects_on_owner_id (owner_id) +# index_applied_transfer_projects_on_project_id (project_id) +# index_applied_transfer_projects_on_user_id (user_id) +# + +class AppliedTransferProject < ApplicationRecord + belongs_to :project + belongs_to :user # 操作者 + belongs_to :owner # 接收个人或组织 + + has_many :applied_messages, as: :applied, dependent: :destroy + + enum status: {canceled: -1, common: 0, accepted: 1, refused: 2} # -1 已取消 0 待操作 1 已接收 2 已拒绝 +end diff --git a/app/models/concerns/project_operable.rb b/app/models/concerns/project_operable.rb index e151613d..b8adf5c8 100644 --- a/app/models/concerns/project_operable.rb +++ b/app/models/concerns/project_operable.rb @@ -11,6 +11,14 @@ module ProjectOperable has_many :team_projects, dependent: :destroy end + def set_owner_permission(creator) + return unless owner.is_a?(Organization) + owner.build_permit_team_projects!(id) + # 避免自己创建的项目,却无法拥有访问权,因为该用户所在团队暂未获得项目访问权 + return if creator.nil? || owner.is_owner?(creator.id) + add_member!(creator.id, "Manager") + end + def add_member!(user_id, role_name='Developer') member = members.create!(user_id: user_id) set_developer_role(member, role_name) @@ -78,12 +86,16 @@ module ProjectOperable if owner.is_a?(User) reporters.exists?(user_id: user.id) elsif owner.is_a?(Organization) - reporters.exists?(user_id: user.id) || owner.is_read?(user.id) + reporters.exists?(user_id: user.id) || owner.is_only_read?(user.id) else false end end + def operator?(user) + user.admin? || !reporter?(user) + end + def set_developer_role(member, role_name) role = Role.find_by(name: role_name) member.member_roles.create!(role: role) @@ -92,4 +104,22 @@ module ProjectOperable def has_menu_permission(unit_type) self.project_units.where(unit_type: unit_type).exists? end + + def all_collaborators + member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager Developer Reporter)}).to_sql + team_user_sql = User.joins(teams: :team_projects).where(team_projects: {project_id: self.id}).to_sql + return User.from("( #{ member_sql } UNION #{ team_user_sql } ) AS users").distinct + end + + def all_developers + member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager Developer)}).to_sql + team_user_sql = User.joins(teams: :team_projects).where(teams: {authorize: %w(owner admin write)}, team_projects: {project_id: self.id}).to_sql + return User.from("( #{ member_sql } UNION #{ team_user_sql } ) AS users").distinct + end + + def all_managers + member_sql = User.joins(members: :roles).where(members: {project_id: self.id}, roles: {name: %w(Manager)}).to_sql + team_user_sql = User.joins(teams: :team_projects).where(teams: {authorize: %w(owner admin)},team_projects: {project_id: self.id}).to_sql + return User.from("( #{ member_sql} UNION #{ team_user_sql } ) AS users").distinct + end end diff --git a/app/models/organization.rb b/app/models/organization.rb index fbf3def7..f12c8b0a 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -108,12 +108,23 @@ class Organization < Owner team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read write admin owner)}).present? end + def is_only_read?(user_id) + team_users.joins(:team).where(user_id: user_id, teams: {authorize: %w(read)}).present? + end + # 是不是所有者团队的最后一个成员 def is_owner_team_last_one?(user_id) owner_team_users = team_users.joins(:team).where(teams: {authorize: %w(owner)}) owner_team_users.pluck(:user_id).include?(user_id) && owner_team_users.size == 1 end + # 为包含组织所有项目的团队创建项目访问权限 + def build_permit_team_projects!(project_id) + teams.where(includes_all_project: true).each do |team| + TeamProject.build(id, team.id, project_id) + end + end + def real_name name = lastname + firstname name = name.blank? ? (nickname.blank? ? login : nickname) : name diff --git a/app/models/owner.rb b/app/models/owner.rb index 1d537a1e..b65ee054 100644 --- a/app/models/owner.rb +++ b/app/models/owner.rb @@ -66,4 +66,6 @@ class Owner < ApplicationRecord has_many :projects, foreign_key: :user_id, dependent: :destroy has_many :repositories, foreign_key: :user_id, dependent: :destroy + has_many :applied_transfer_projects, dependent: :destroy + end diff --git a/app/models/project.rb b/app/models/project.rb index 0260a407..b4868083 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -113,6 +113,7 @@ class Project < ApplicationRecord has_one :project_detail, dependent: :destroy has_many :team_projects, dependent: :destroy has_many :project_units, dependent: :destroy + has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy after_save :check_project_members scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)} @@ -296,4 +297,7 @@ class Project < ApplicationRecord update_column(:updated_on, time) end + def is_transfering + applied_transfer_project&.common? ? true : false + end end diff --git a/app/models/project_unit.rb b/app/models/project_unit.rb index 0f427534..a07f62b4 100644 --- a/app/models/project_unit.rb +++ b/app/models/project_unit.rb @@ -20,13 +20,18 @@ class ProjectUnit < ApplicationRecord validates :unit_type, uniqueness: { scope: :project_id} - def self.init_types(project_id) - ProjectUnit::unit_types.each do |_, v| + def self.init_types(project_id, project_type='common') + unit_types = project_type == 'sync_mirror' ? ProjectUnit::unit_types.except("pulls") : ProjectUnit::unit_types + unit_types.each do |_, v| self.create!(project_id: project_id, unit_type: v) end end def self.update_by_unit_types!(project, types) + # 同步镜像项目不能有合并请求模块 + types.delete("pulls") if project.sync_mirror? + # 默认code类型自动创建 + types << "code" project.project_units.where.not(unit_type: types).each(&:destroy!) types.each do |type| project.project_units.find_or_create_by!(unit_type: type) diff --git a/app/models/repository.rb b/app/models/repository.rb index 978bc3c5..0daa4b91 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -22,6 +22,8 @@ # version_releases_count :integer default("0") # fork_url :string(255) # is_mirror :boolean default("0") +# accelerator_url :string(255) default("") +# source_clone_url :string(255) default("") # # Indexes # @@ -76,4 +78,9 @@ class Repository < ApplicationRecord end end + def remote_mirror_url + source_clone_url.blank? ? mirror_url : source_clone_url + end + + end diff --git a/app/models/team.rb b/app/models/team.rb index d17827da..c2596390 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -33,9 +33,10 @@ class Team < ApplicationRecord enum authorize: {common: 0, read: 1, write: 2, admin: 3, owner: 4} - def self.build(organization_id, name, description, authorize, includes_all_project, can_create_org_project) + def self.build(organization_id, name, nickname, description, authorize, includes_all_project, can_create_org_project) self.create!(organization_id: organization_id, name: name, + nickname: nickname, description: description, authorize: authorize, includes_all_project: includes_all_project, diff --git a/app/models/user.rb b/app/models/user.rb index e16e5ca0..256cc7b4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -147,9 +147,14 @@ class User < Owner has_many :trail_auth_apply_actions, -> { where(container_type: 'TrialAuthorization') }, class_name: 'ApplyAction' # has_many :attendances - + has_many :applied_messages, dependent: :destroy + has_many :operate_applied_messages, class_name: 'AppliedMessage', dependent: :destroy # 项目 has_many :applied_projects, dependent: :destroy + has_many :operate_applied_transfer_projects, class_name: 'AppliedTransferProject', dependent: :destroy + has_many :members, dependent: :destroy + has_many :team_users, dependent: :destroy + has_many :teams, through: :team_users # 教学案例 # has_many :libraries, dependent: :destroy diff --git a/app/queries/projects/list_my_query.rb b/app/queries/projects/list_my_query.rb index 8ad0d0b0..5bda5612 100644 --- a/app/queries/projects/list_my_query.rb +++ b/app/queries/projects/list_my_query.rb @@ -18,7 +18,9 @@ class Projects::ListMyQuery < ApplicationQuery end if params[:category].blank? - projects = projects.members_projects(user.id) + normal_projects = projects.members_projects(user.id).to_sql + org_projects = projects.joins(team_projects: [team: :team_users]).where(team_users: {user_id: user.id}).to_sql + projects = Project.from("( #{ normal_projects} UNION #{ org_projects } ) AS projects").distinct elsif params[:category].to_s == "join" normal_projects = projects.where.not(user_id: user.id).members_projects(user.id).to_sql org_projects = projects.joins(team_projects: [team: :team_users]).where(team_users: {user_id: user.id}).to_sql diff --git a/app/services/gitea/accelerator/migrate_service.rb b/app/services/gitea/accelerator/migrate_service.rb new file mode 100644 index 00000000..015a09b1 --- /dev/null +++ b/app/services/gitea/accelerator/migrate_service.rb @@ -0,0 +1,148 @@ +class Gitea::Accelerator::MigrateService < ApplicationService + attr_reader :params + + # params description: + # { + # auth_username string + # clone_addr* string #clone地址 + # description string + # issues boolean + # labels boolean + # milestones boolean + # mirror boolean + # private boolean + # pull_requests boolean + # releases boolean + # repo_name* string #仓库名称 + # uid* integer($int64) #gitea用户id或组织id + # wiki boolean + # } + # EX: + # params = { + # clone_addr: 'xxx.com', + # repo_name: 'repo_name', + # uid: 2, + # private: false + # } + + def initialize(params) + @params = params + end + + def call + return error('[gitea:] accelerator config missing') if check_accelerator! + response = post(url, request_params) + + render_status(response) + end + + private + + def request_params + { + uid: access_uid, + clone_addr: params[:clone_addr], + repo_name: params[:repository_name], + auth_username: params[:auth_username], + auth_password: params[:auth_password], + mirror: ActiveModel::Type::Boolean.new.cast(params[:is_mirror]) + } + end + + def url + "/repos/migrate".freeze + end + + def post(url, params) + puts "[gitea] request params: #{params}" + puts "[gitea] access_username: #{access_username}" + puts "[gitea] access_password: #{access_password}" + conn.post do |req| + req.url full_url(url) + req.body = params.to_json + end + end + + def conn + @client ||= begin + Faraday.new(url: domain) do |req| + req.request :url_encoded + req.headers['Content-Type'] = 'application/json' + req.response :logger # 显示日志 + req.adapter Faraday.default_adapter + req.basic_auth(access_username, access_password) + end + end + @client + end + + def base_url + accelerator["base_url"] + end + + def domain + accelerator["domain"] + end + + def api_url + [domain, base_url].join('') + end + + def full_url(api_rest, action='post') + url = [api_url, api_rest].join('').freeze + url = action === 'get' ? url : URI.escape(url) + puts "[gitea] request url: #{url}" + url + end + + def access_username + accelerator["access_key_id"] + end + + def access_password + accelerator["access_key_secret"] + end + + def access_uid + accelerator["access_admin_uid"] + end + + def accelerator + Gitea.gitea_config[:accelerator] + end + + def render_status(response) + puts "[gitea] response status: #{response.status}" + puts "[gitea] response body: #{response.body}" + case response.status + when 201 + success + when 403 + error('APIForbiddenError') + when 422 + error('APIValidationError') + else + error("MigrateError") + end + end + + def error(message) + { + status: :error, + message: message, + data: nil + } + end + + def success(data=nil) + { + status: :success, + message: nil, + data: data + } + end + + def check_accelerator! + accelerator.blank? || access_username.blank? || access_password.blank? || domain.blank? + end +end \ No newline at end of file diff --git a/app/services/gitea/client_service.rb b/app/services/gitea/client_service.rb index 1c13bc42..0ef04199 100644 --- a/app/services/gitea/client_service.rb +++ b/app/services/gitea/client_service.rb @@ -176,6 +176,25 @@ class Gitea::ClientService < ApplicationService [status, message, body] end + def render_gitea_response(response) + status = response.status + body = response&.body + + log_error(status, body) + message = nil + begin + translate = YAML.load(File.read('config/gitea_response.yml')) + + self.class.to_s.underscore.split("/").map{|i| translate=translate[i]} + message = body.nil? ? translate[status]['default'] : JSON.parse(body)['message'] + message = translate[status][message].nil? ? message : translate[status][message] + + return [status, message] + rescue + return [status, message] + end + end + def get_body_by_status(status, body) body, message = case status diff --git a/app/services/gitea/pull_request/merge_service.rb b/app/services/gitea/pull_request/merge_service.rb index c33c8781..bdf2b23b 100644 --- a/app/services/gitea/pull_request/merge_service.rb +++ b/app/services/gitea/pull_request/merge_service.rb @@ -20,7 +20,7 @@ class Gitea::PullRequest::MergeService < Gitea::ClientService def call response = post(url, request_params) - render_200_no_body(response) + render_gitea_response(response) end private diff --git a/app/services/organizations/create_service.rb b/app/services/organizations/create_service.rb index 96bb3f46..a430ed6f 100644 --- a/app/services/organizations/create_service.rb +++ b/app/services/organizations/create_service.rb @@ -54,7 +54,7 @@ class Organizations::CreateService < ApplicationService end def create_owner_info - @owner_team = Team.build(organization.id, "Owners", "", 4, true, true) + @owner_team = Team.build(organization.id, "Owners", "Owner团队", "", 4, true, true) TeamUnit.unit_types.keys.each do |u_type| TeamUnit.build(organization.id, owner_team.id, u_type) end diff --git a/app/services/organizations/teams/create_service.rb b/app/services/organizations/teams/create_service.rb index 81ad7877..c3fc5f59 100644 --- a/app/services/organizations/teams/create_service.rb +++ b/app/services/organizations/teams/create_service.rb @@ -28,6 +28,10 @@ class Organizations::Teams::CreateService < ApplicationService params[:name] end + def nickname + params[:nickname] + end + def description params[:description] end @@ -45,7 +49,7 @@ class Organizations::Teams::CreateService < ApplicationService end def create_team - @team = Team.build(org.id, name, description, authorize, + @team = Team.build(org.id, name, nickname, description, authorize, includes_all_project, can_create_org_project) end diff --git a/app/services/projects/accept_transfer_service.rb b/app/services/projects/accept_transfer_service.rb new file mode 100644 index 00000000..c99885ca --- /dev/null +++ b/app/services/projects/accept_transfer_service.rb @@ -0,0 +1,49 @@ +class Projects::AcceptTransferService < ApplicationService + attr_accessor :applied_transfer_project, :owner + attr_reader :user, :project + + def initialize(user, project) + @user = user + @project = project + @applied_transfer_project = project.applied_transfer_project + @owner = @applied_transfer_project.owner + end + + def call + Rails.logger.info("###### Project accept_transfer_service begin ######") + ActiveRecord::Base.transaction do + validate! + update_apply + operate_project + send_apply_message + end + + Rails.logger.info("##### Project accept_transfer_service end ######") + + + return @applied_transfer_project + end + + private + def validate! + raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering + raise Error, '未拥有接受转移权限' unless is_permit_operator + end + + def is_permit_operator + return true if @user == @owner + return @owner.is_a?(Organization) && @owner.is_admin?(@user) + end + + def update_apply + @applied_transfer_project.update!(status: 'accepted') + end + + def operate_project + @project = Projects::TransferService.call(@project, @owner) + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'successed') + end +end \ No newline at end of file diff --git a/app/services/projects/apply_transfer_service.rb b/app/services/projects/apply_transfer_service.rb new file mode 100644 index 00000000..28097bed --- /dev/null +++ b/app/services/projects/apply_transfer_service.rb @@ -0,0 +1,43 @@ +class Projects::ApplyTransferService < ApplicationService + attr_accessor :owner, :applied_transfer_project + attr_reader :user, :project, :params + + def initialize(user, project, params) + @user = user + @project = project + @params = params + @owner = Owner.find_by(login: params[:owner_name]) + end + + def call + Rails.logger.info("###### Project apply_transfer_service begin ######") + validate! + create_apply + send_apply_message + Rails.logger.info("###### Project apply_transfer_service end ######") + + return @applied_transfer_project + end + + private + def validate! + raise Error, '仓库标识不正确' if @project.identifier != params[:identifier] + raise Error, '该仓库正在迁移' if @project.is_transfering + raise Error, '新拥有者不存在' unless @owner.present? + raise Error, '新拥有者已经存在同名仓库!' if Project.where(user_id: @owner.id, identifier: params[:identifier]).present? + raise Error, '未拥有转移权限' unless is_permit_owner + end + + def is_permit_owner + return true unless @owner.is_a?(Organization) + return @owner.is_owner?(@user) + end + + def create_apply + @applied_transfer_project = AppliedTransferProject.create!(user_id: user.id, project_id: project.id, owner_id: @owner.id) + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'common') + end +end \ No newline at end of file diff --git a/app/services/projects/cancel_transfer_service.rb b/app/services/projects/cancel_transfer_service.rb new file mode 100644 index 00000000..153b24ff --- /dev/null +++ b/app/services/projects/cancel_transfer_service.rb @@ -0,0 +1,33 @@ +class Projects::CancelTransferService < ApplicationService + attr_accessor :applied_transfer_project + attr_reader :user, :project + + def initialize(user, project) + @user = user + @project = project + @applied_transfer_project = project.applied_transfer_project + end + + def call + Rails.logger.info("###### Project cancel_transfer_service begin ######") + validate! + update_apply + send_apply_message + Rails.logger.info("###### Project cancel_transfer_service end ######") + + return @applied_transfer_project + end + + private + def validate! + raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering + end + + def update_apply + @applied_transfer_project.update!(status: 'canceled') + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'canceled') + end +end \ No newline at end of file diff --git a/app/services/projects/migrate_service.rb b/app/services/projects/migrate_service.rb index d1e14088..7df08f9e 100644 --- a/app/services/projects/migrate_service.rb +++ b/app/services/projects/migrate_service.rb @@ -1,5 +1,6 @@ class Projects::MigrateService < ApplicationService attr_reader :user, :params + attr_accessor :project def initialize(user, params) @user = user @@ -9,8 +10,9 @@ class Projects::MigrateService < ApplicationService def call @project = Project.new(project_params) if @project.save! - ProjectUnit.init_types(@project.id) + ProjectUnit.init_types(@project.id, project.project_type) Project.update_mirror_projects_count! + @project.set_owner_permission(user) Repositories::MigrateService.new(user, @project, repository_params).call else # @@ -48,7 +50,8 @@ class Projects::MigrateService < ApplicationService user_id: params[:user_id], login: params[:auth_username], password: params[:auth_password], - is_mirror: params[:is_mirror] + is_mirror: params[:is_mirror], + source_clone_url: params[:source_clone_url] } end diff --git a/app/services/projects/refuse_transfer_service.rb b/app/services/projects/refuse_transfer_service.rb new file mode 100644 index 00000000..1b81b574 --- /dev/null +++ b/app/services/projects/refuse_transfer_service.rb @@ -0,0 +1,40 @@ +class Projects::RefuseTransferService < ApplicationService + attr_accessor :applied_transfer_project, :owner + attr_reader :user, :project + + def initialize(user, project) + @user = user + @project = project + @applied_transfer_project = project.applied_transfer_project + @owner = @applied_transfer_project.owner + end + + def call + Rails.logger.info("###### Project refuse_transfer_service begin ######") + validate! + update_apply + send_apply_message + Rails.logger.info("###### Project refuse_transfer_service end ######") + + return @applied_transfer_project + end + + private + def validate! + raise Error, '该仓库未在迁移' unless @applied_transfer_project.present? && @project.is_transfering + raise Error, '未拥有拒绝转移权限' unless is_permit_operator + end + + def is_permit_operator + return true if @user == @owner + return @owner.is_a?(Organization) && @owner.is_admin?(@user) + end + + def update_apply + @applied_transfer_project.update!(status: 'refused') + end + + def send_apply_message + SendTransferProjectAppliedMessageJob.perform_now(@applied_transfer_project, @user, 'failure') + end +end \ No newline at end of file diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index b32b2481..a2da34f0 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -23,6 +23,7 @@ class Projects::TransferService < ApplicationService private def update_owner + project.members.find_by(user_id: owner.id).destroy! if owner.is_a?(User) project.update!(user_id: new_owner.id) end @@ -32,9 +33,8 @@ class Projects::TransferService < ApplicationService def update_visit_teams if new_owner.is_a?(Organization) - new_owner.teams.where(includes_all_project: true).each do |team| - TeamProject.build(new_owner.id, team.id, project.id) - end + # 为包含组织所有项目的团队创建项目访问权限 + new_owner.build_permit_team_projects(project.id) else project.team_projects.each(&:destroy!) end diff --git a/app/services/pull_requests/merge_service.rb b/app/services/pull_requests/merge_service.rb index a20ee3aa..d5fc102e 100644 --- a/app/services/pull_requests/merge_service.rb +++ b/app/services/pull_requests/merge_service.rb @@ -1,6 +1,6 @@ class PullRequests::MergeService < ApplicationService attr_reader :owner, :repo, :pull, :current_user, :params - + attr_accessor :status, :message # eq: # PullRequests::MergeService.call(owner, repo, pull, current_user, params) def initialize(owner, repo, pull, current_user, params) @@ -15,6 +15,7 @@ class PullRequests::MergeService < ApplicationService ActiveRecord::Base.transaction do gitea_pull_merge! end + self end private @@ -22,8 +23,7 @@ class PullRequests::MergeService < ApplicationService def gitea_pull_merge! result = Gitea::PullRequest::MergeService.call(@current_user.gitea_token, @owner.login, @repo.identifier, @pull.gpid, gitea_merge_pull_params) - - result[:status] === 200 ? true : false + @status, @message = result end def gitea_merge_pull_params diff --git a/app/services/repositories/create_service.rb b/app/services/repositories/create_service.rb index 18e72644..f0c584e1 100644 --- a/app/services/repositories/create_service.rb +++ b/app/services/repositories/create_service.rb @@ -15,6 +15,7 @@ class Repositories::CreateService < ApplicationService create_gitea_repository sync_project sync_repository + @project.set_owner_permission(user) # if project.project_type == "common" # chain_params = { # type: "create", @@ -44,19 +45,9 @@ class Repositories::CreateService < ApplicationService @gitea_repository = Gitea::Repository::CreateService.new(user.gitea_token, gitea_repository_params).call elsif project.owner.is_a?(Organization) @gitea_repository = Gitea::Organization::Repository::CreateService.call(user.gitea_token, project.owner.login, gitea_repository_params) - project.owner.teams.each do |team| - next unless team.includes_all_project - TeamProject.build(project.user_id, team.id, project.id) - end - create_manager_member end end - def create_manager_member - return if project.owner.is_owner?(user.id) - project.add_member!(user.id, "Manager") - end - def sync_project if gitea_repository project.update_columns( diff --git a/app/services/repositories/migrate_service.rb b/app/services/repositories/migrate_service.rb index 156777cc..b737b2ef 100644 --- a/app/services/repositories/migrate_service.rb +++ b/app/services/repositories/migrate_service.rb @@ -21,7 +21,9 @@ class Repositories::MigrateService < ApplicationService private def repository_params - params.merge(project_id: project.id, identifier: params[:identifier]) + params.merge(project_id: project.id, + identifier: params[:identifier], + source_clone_url: params[:source_clone_url]) end def gitea_repository_params diff --git a/app/views/admins/projects/shared/_list.html.erb b/app/views/admins/projects/shared/_list.html.erb index bd630028..fe8d96e9 100644 --- a/app/views/admins/projects/shared/_list.html.erb +++ b/app/views/admins/projects/shared/_list.html.erb @@ -23,7 +23,7 @@
获取当前登陆用户信息
@@ -622,7 +649,818 @@ Success — a happy kitten is an authenticated kitten! -待办事项-用户通知信息
+ +++示例:
+
curl -X GET http://localhost:3000/api/users/yystopf/applied_messages.json
+
await octokit.request('GET /api/users/:login/applied_messages.json')
+
GET /api/users/:login/applied_messages.json
参数 | +类型 | +字段说明 | +
---|---|---|
login | +string | +用户标识 | +
参数 | +类型 | +字段说明 | +
---|---|---|
applied | +object | +通知主体 | +
applied.id | +int | +通知主体的迁移id | +
applied.status | +string | +通知主体的迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝 | +
applied.time_ago | +string | +通知主体的迁移创建的时间 | +
applied.project.id | +int | +通知主体的迁移项目的id | +
applied.project.identifier | +string | +通知主体的迁移项目的标识 | +
applied.project.name | +string | +通知主体的迁移项目的名称 | +
applied.project.description | +string | +通知主体的迁移项目的描述 | +
applied.project.is_public | +bool | +通知主体的迁移项目是否公开 | +
applied.project.owner.id | +bool | +通知主体的迁移项目拥有者id | +
applied.project.owner.type | +string | +通知主体的迁移项目拥有者类型 | +
applied.project.owner.name | +string | +通知主体的迁移项目拥有者昵称 | +
applied.project.owner.login | +string | +通知主体的迁移项目拥有者标识 | +
applied.project.owner.image_url | +string | +通知主体的迁移项目拥有者头像 | +
applied.user.id | +int | +通知主体的迁移创建者的id | +
applied.user.type | +string | +通知主体的迁移创建者的类型 | +
applied.user.name | +string | +通知主体的迁移创建者的名称 | +
applied.user.login | +string | +通知主体的迁移创建者的标识 | +
applied.user.image_url | +string | +通知主体的迁移创建者头像 | +
applied.owner.id | +int | +通知主体的迁移接受者的id | +
applied.owner.type | +string | +通知主体的迁移接受者的类型 | +
applied.owner.name | +string | +通知主体的迁移接受者的名称 | +
applied.owner.login | +string | +通知主体的迁移接受者的标识 | +
applied.owner.image_url | +string | +通知主体的迁移接受者头像 | +
applied_type | +string | +通知类型 | +
name | +string | +通知内容 | +
viewed | +string | +是否已读,waiting:未读,viewed:已读 | +
status | +string | +通知状态, canceled:已取消,common: 正常,successed:成功,failure:失败 | +
time_ago | +string | +通知时间 | +
++返回的JSON示例:
+
{
+ "total_count": 5,
+ "applied_messages": [
+ {
+ "applied": {
+ "project": {
+ "id": 86,
+ "identifier": "ceshi_repo1",
+ "name": "测试项目啊1",
+ "description": "二十多",
+ "is_public": true,
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ }
+ },
+ "user": {
+ "id": 6,
+ "type": "User",
+ "name": "yystopf",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
+ },
+ "owner": {
+ "id": 9,
+ "type": "Organization",
+ "name": "测试组织",
+ "login": "ceshi_org",
+ "image_url": "images/avatars/Organization/9?t=1612706073"
+ },
+ "id": 4,
+ "status": "common",
+ "created_at": "2021-04-26 09:54",
+ "time_ago": "35分钟前"
+ },
+ "applied_user": {
+ "id": 6,
+ "type": "User",
+ "name": "yystopf",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
+ },
+ "applied_type": "AppliedTransferProject",
+ "name": "正在将【测试项目啊1】仓库转移给【测试组织】",
+ "viewed": "viewed",
+ "status": "common",
+ "created_at": "2021-04-26 09:54",
+ "time_ago": "35分钟前"
+ },
+ ...
+ ]
+}
+
待办事项-接受仓库
+ +++示例:
+
curl -X GET http://localhost:3000/api/users/yystopf/applied_transfer_projects.json
+
await octokit.request('GET /api/users/:login/applied_transfer_projects.json')
+
GET /api/users/:login/applied_transfer_projects.json
参数 | +类型 | +字段说明 | +
---|---|---|
login | +string | +用户标识 | +
参数 | +类型 | +字段说明 | +
---|---|---|
id | +int | +迁移id | +
status | +string | +迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝 | +
time_ago | +string | +迁移创建的时间 | +
project.id | +int | +迁移项目的id | +
project.identifier | +string | +迁移项目的标识 | +
project.name | +string | +迁移项目的名称 | +
project.description | +string | +迁移项目的描述 | +
project.is_public | +bool | +迁移项目是否公开 | +
project.owner.id | +bool | +迁移项目拥有者id | +
project.owner.type | +string | +迁移项目拥有者类型 | +
project.owner.name | +string | +迁移项目拥有者昵称 | +
project.owner.login | +string | +迁移项目拥有者标识 | +
project.owner.image_url | +string | +迁移项目拥有者头像 | +
user.id | +int | +迁移创建者的id | +
user.type | +string | +迁移创建者的类型 | +
user.name | +string | +迁移创建者的名称 | +
user.login | +string | +迁移创建者的标识 | +
user.image_url | +string | +迁移创建者头像 | +
owner.id | +int | +迁移接受者的id | +
owner.type | +string | +迁移接受者的类型 | +
owner.name | +string | +迁移接受者的名称 | +
owner.login | +string | +迁移接受者的标识 | +
owner.image_url | +string | +迁移接受者头像 | +
++返回的JSON示例:
+
{
+ "total_count": 4,
+ "applied_transfer_projects": [
+ {
+ "project": {
+ "id": 86,
+ "identifier": "ceshi_repo1",
+ "name": "测试项目啊1",
+ "description": "二十多",
+ "is_public": true,
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ }
+ },
+ "user": {
+ "id": 6,
+ "type": "User",
+ "name": "yystopf",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
+ },
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ },
+ "id": 1,
+ "status": "canceled",
+ "created_at": "2021-04-25 18:06",
+ "time_ago": "16小时前"
+ },
+ ...
+ ]
+}
+
用户接受迁移
+ +++示例:
+
curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/accept.json
+
await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/accept.json')
+
GET /api/users/:login/applied_transfer_projects/:id/accept.json
参数 | +类型 | +字段说明 | +
---|---|---|
login | +string | +用户标识 | +
id | +int | +迁移id | +
参数 | +类型 | +字段说明 | +
---|---|---|
id | +int | +迁移id | +
status | +string | +迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝 | +
time_ago | +string | +迁移创建的时间 | +
project.id | +int | +迁移项目的id | +
project.identifier | +string | +迁移项目的标识 | +
project.name | +string | +迁移项目的名称 | +
project.description | +string | +迁移项目的描述 | +
project.is_public | +bool | +迁移项目是否公开 | +
project.owner.id | +bool | +迁移项目拥有者id | +
project.owner.type | +string | +迁移项目拥有者类型 | +
project.owner.name | +string | +迁移项目拥有者昵称 | +
project.owner.login | +string | +迁移项目拥有者标识 | +
project.owner.image_url | +string | +迁移项目拥有者头像 | +
user.id | +int | +迁移创建者的id | +
user.type | +string | +迁移创建者的类型 | +
user.name | +string | +迁移创建者的名称 | +
user.login | +string | +迁移创建者的标识 | +
user.image_url | +string | +迁移创建者头像 | +
owner.id | +int | +迁移接受者的id | +
owner.type | +string | +迁移接受者的类型 | +
owner.name | +string | +迁移接受者的名称 | +
owner.login | +string | +迁移接受者的标识 | +
owner.image_url | +string | +迁移接受者头像 | +
++返回的JSON示例:
+
{
+ "project": {
+ "id": 86,
+ "identifier": "ceshi_repo1",
+ "name": "测试项目啊1",
+ "description": "二十多",
+ "is_public": true,
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ }
+ },
+ "user": {
+ "id": 6,
+ "type": "User",
+ "name": "yystopf",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
+ },
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ },
+ "id": 1,
+ "status": "canceled",
+ "created_at": "2021-04-25 18:06",
+ "time_ago": "16小时前"
+}
+
用户拒绝迁移
+ +++示例:
+
curl -X POST http://localhost:3000/api/users/yystopf/applied_transfer_projects/2/refuse.json
+
await octokit.request('GET /api/users/:login/applied_transfer_projects/:id/refuse.json')
+
GET /api/users/:login/applied_transfer_projects/:id/refuse.json
参数 | +类型 | +字段说明 | +
---|---|---|
login | +string | +用户标识 | +
id | +int | +迁移id | +
参数 | +类型 | +字段说明 | +
---|---|---|
id | +int | +迁移id | +
status | +string | +迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝 | +
time_ago | +string | +迁移创建的时间 | +
project.id | +int | +迁移项目的id | +
project.identifier | +string | +迁移项目的标识 | +
project.name | +string | +迁移项目的名称 | +
project.description | +string | +迁移项目的描述 | +
project.is_public | +bool | +迁移项目是否公开 | +
project.owner.id | +bool | +迁移项目拥有者id | +
project.owner.type | +string | +迁移项目拥有者类型 | +
project.owner.name | +string | +迁移项目拥有者昵称 | +
project.owner.login | +string | +迁移项目拥有者标识 | +
project.owner.image_url | +string | +迁移项目拥有者头像 | +
user.id | +int | +迁移创建者的id | +
user.type | +string | +迁移创建者的类型 | +
user.name | +string | +迁移创建者的名称 | +
user.login | +string | +迁移创建者的标识 | +
user.image_url | +string | +迁移创建者头像 | +
owner.id | +int | +迁移接受者的id | +
owner.type | +string | +迁移接受者的类型 | +
owner.name | +string | +迁移接受者的名称 | +
owner.login | +string | +迁移接受者的标识 | +
owner.image_url | +string | +迁移接受者头像 | +
++返回的JSON示例:
+
{
+ "project": {
+ "id": 86,
+ "identifier": "ceshi_repo1",
+ "name": "测试项目啊1",
+ "description": "二十多",
+ "is_public": true,
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ }
+ },
+ "user": {
+ "id": 6,
+ "type": "User",
+ "name": "yystopf",
+ "login": "yystopf",
+ "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png"
+ },
+ "owner": {
+ "id": 52,
+ "type": "Organization",
+ "name": "身份卡手动阀",
+ "login": "ceshi1",
+ "image_url": "images/avatars/Organization/52?t=1618805056"
+ },
+ "id": 1,
+ "status": "canceled",
+ "created_at": "2021-04-25 18:06",
+ "time_ago": "16小时前"
+}
+
获取项目列表,也可以更加相关条件过滤搜素
@@ -1590,6 +2428,502 @@ http://localhost:3000/api/projects/migrate.json "id": 3290, "identifier": "newadm" } +用户管理的组织列表
+用户管理的组织列表
+ +++示例:
+curl -X GET \ +http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizations.json | jq +
await octokit.request('GET /api/:owner/:repo/applied_transfer_projects/organizations') +
HTTP 请求
++
GET api/:owner/:repo/applied_transfer_projects/organizations
请求参数
++
++ + +参数 +必选 +默认 +类型 +字段说明 ++ +owner +是 ++ string +用户登录名 ++ +repo +是 ++ string +项目标识identifier +返回字段说明
++
+ ++ + +参数 +类型 +字段说明 ++ +name +string +组织标识 ++ +nickname +string +组织名称 ++ +description +string +组织描述 ++ +avatar_url +string|组织头像 ++ ++返回的JSON示例:
+{ + "total_count": 3, + "organizations": [ + { + "id": 9, + "name": "ceshi_org", + "nickname": "测试组织", + "description": "测试组织", + "avatar_url": "images/avatars/Organization/9?t=1612706073" + }, + { + "id": 51, + "name": "ceshi", + "nickname": "测试组织哈哈哈", + "description": "23212312", + "avatar_url": "images/avatars/Organization/51?t=1618800723" + }, + { + "id": 52, + "name": "ceshi1", + "nickname": "身份卡手动阀", + "description": "1231手动阀是的", + "avatar_url": "images/avatars/Organization/52?t=1618805056" + } + ] +} +
迁移项目
+迁移项目,edit接口is_transfering为true表示正在迁移
+ +++示例:
+curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects.json +
await octokit.request('POST /api/:owner/:repo/applied_transfer_projects.json') +
HTTP 请求
++
POST /api/:owner/:repo/applied_transfer_projects.json
请求参数
++
++ + +参数 +必选 +默认 +类型 +字段说明 ++ +owner +是 ++ string +用户登录名 ++ +repo +是 ++ string +项目标识identifier ++ +owner_name +是 ++ string +迁移对象标识 +返回字段说明
++
+ ++ + +参数 +类型 +字段说明 ++ +id +int +项目id ++ +status +string +项目迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝 ++ +time_ago +string +项目迁移创建的时间 ++ +project.id +int +迁移项目的id ++ +project.identifier +string +迁移项目的标识 ++ +project.name +string +迁移项目的名称 ++ +project.description +string +迁移项目的描述 ++ +project.is_public +bool +迁移项目是否公开 ++ +project.owner.id +bool +迁移项目拥有者id ++ +project.owner.type +string +迁移项目拥有者类型 ++ +project.owner.name +string +迁移项目拥有者昵称 ++ +project.owner.login +string +迁移项目拥有者标识 ++ +project.owner.image_url +string +迁移项目拥有者头像 ++ +user.id +int +迁移创建者的id ++ +user.type +string +迁移创建者的类型 ++ +user.name +string +迁移创建者的名称 ++ +user.login +string +迁移创建者的标识 ++ +user.image_url +string +迁移创建者头像 ++ +owner.id +int +迁移接受者的id ++ +owner.type +string +迁移接受者的类型 ++ +owner.name +string +迁移接受者的名称 ++ +owner.login +string +迁移接受者的标识 ++ +owner.image_url +string +迁移接受者头像 +++返回的JSON示例:
+{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "1分钟前" +} +
取消迁移项目
+迁移项目,edit接口is_transfering为true表示正在迁移
+ +++示例:
+curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/cancel.json +
await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.json') +
HTTP 请求
++
POST /api/:owner/:repo/applied_transfer_projects/cancel.json
请求参数
++
++ + +参数 +必选 +默认 +类型 +字段说明 ++ +owner +是 ++ string +用户登录名 ++ +repo +是 ++ string +项目标识identifier +返回字段说明
++
+ ++ + +参数 +类型 +字段说明 ++ +id +int +迁移id ++ +status +string +迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝 ++ +time_ago +string +迁移创建的时间 ++ +project.id +int +迁移项目的id ++ +project.identifier +string +迁移项目的标识 ++ +project.name +string +迁移项目的名称 ++ +project.description +string +迁移项目的描述 ++ +project.is_public +bool +迁移项目是否公开 ++ +project.owner.id +bool +迁移项目拥有者id ++ +project.owner.type +string +迁移项目拥有者类型 ++ +project.owner.name +string +迁移项目拥有者昵称 ++ +project.owner.login +string +迁移项目拥有者标识 ++ +project.owner.image_url +string +迁移项目拥有者头像 ++ +user.id +int +迁移创建者的id ++ +user.type +string +迁移创建者的类型 ++ +user.name +string +迁移创建者的名称 ++ +user.login +string +迁移创建者的标识 ++ +user.image_url +string +迁移创建者头像 ++ +owner.id +int +迁移接受者的id ++ +owner.type +string +迁移接受者的类型 ++ +owner.name +string +迁移接受者的名称 ++ +owner.login +string +迁移接受者的标识 ++ +owner.image_url +string +迁移接受者头像 +++返回的JSON示例:
+{ + "project": { + "id": 86, + "identifier": "ceshi_repo1", + "name": "测试项目啊1", + "description": "二十多", + "is_public": true, + "owner": { + "id": 52, + "type": "Organization", + "name": "身份卡手动阀", + "login": "ceshi1", + "image_url": "images/avatars/Organization/52?t=1618805056" + } + }, + "user": { + "id": 6, + "type": "User", + "name": "yystopf", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/Y/241_125_89/120.png" + }, + "owner": { + "id": 9, + "type": "Organization", + "name": "测试组织", + "login": "ceshi_org", + "image_url": "images/avatars/Organization/9?t=1612706073" + }, + "id": 4, + "status": "common", + "created_at": "2021-04-26 09:54", + "time_ago": "1分钟前" +}
Repositories
仓库详情
仓库详情