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 @@ <%= list_index_no((params[:page] || 1).to_i, index) %> <%= project.id %> - <%= link_to(project.name, "/projects/#{project.id}", target: '_blank') %> + <%= link_to(project.name, "/projects/#{project&.owner&.login}/#{project.identifier}", target: '_blank') %> <%= project.is_public ? '√' : '' %> <%= project.issues.size %> @@ -33,7 +33,7 @@ <%= project.versions.size %> <%= project.members.size %> - <%= project.owner ? link_to(project.owner&.real_name, "/users/#{project.owner&.login}", target: '_blank') : "" %> + <%= link_to_project(project) %> <%= project.created_on&.strftime('%Y-%m-%d %H:%M') %> diff --git a/app/views/compare/show.json.jbuilder b/app/views/compare/show.json.jbuilder index 8257cc7f..8b75fa3d 100644 --- a/app/views/compare/show.json.jbuilder +++ b/app/views/compare/show.json.jbuilder @@ -4,7 +4,7 @@ json.commits do json.array! @compare_result['Commits'] do |commit| json.author do # TODO: 获取头像地址待优化 - forge_user = User.includes(:user_extension).select(:id, :login).find_by(login: commit['Author']['Name']) + forge_user = User.includes(:user_extension).find_by(login: commit['Author']['Name']) json.login commit['Author']['Name'] json.name commit['Author']['Name'] json.image_url forge_user.nil? ? '' : url_to_avatar(forge_user) @@ -12,7 +12,7 @@ json.commits do json.committer do # TODO: 获取头像地址待优化 - forge_user = User.includes(:user_extension).select(:id, :login).find_by(login: commit['Committer']['Name']) + forge_user = User.includes(:user_extension).find_by(login: commit['Committer']['Name']) json.login commit['Committer']['Name'] json.name commit['Committer']['Name'] json.image_url forge_user.nil? ? '' : url_to_avatar(forge_user) diff --git a/app/views/organizations/organizations/_simple.json.jbuilder b/app/views/organizations/organizations/_simple.json.jbuilder new file mode 100644 index 00000000..12792b12 --- /dev/null +++ b/app/views/organizations/organizations/_simple.json.jbuilder @@ -0,0 +1,5 @@ +json.id organization.id +json.name organization.login +json.nickname organization.nickname.blank? ? organization.name : organization.nickname +json.description organization.description +json.avatar_url url_to_avatar(organization) diff --git a/app/views/projects/applied_transfer_projects/_detail.json.jbuilder b/app/views/projects/applied_transfer_projects/_detail.json.jbuilder new file mode 100644 index 00000000..e583ed1d --- /dev/null +++ b/app/views/projects/applied_transfer_projects/_detail.json.jbuilder @@ -0,0 +1,21 @@ +project = object.project +json.project do + json.id project.id + json.identifier project.identifier + json.name project.name + json.description project.description + json.is_public project.is_public + json.owner do + json.partial! "/users/user_simple", locals: {user: project.owner} + end +end +json.user do + json.partial! "/users/user_simple", locals: {user: object.user} +end +json.owner do + json.partial! "/users/user_simple", locals: {user: object.owner} +end +json.id object.id +json.status object.status +json.created_at format_time(object.created_at) +json.time_ago time_from_now(object.created_at) diff --git a/app/views/projects/applied_transfer_projects/cancel.json.jbuilder b/app/views/projects/applied_transfer_projects/cancel.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/projects/applied_transfer_projects/cancel.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/projects/applied_transfer_projects/create.json.jbuilder b/app/views/projects/applied_transfer_projects/create.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/projects/applied_transfer_projects/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/projects/applied_transfer_projects/organizations.json.jbuilder b/app/views/projects/applied_transfer_projects/organizations.json.jbuilder new file mode 100644 index 00000000..772910f0 --- /dev/null +++ b/app/views/projects/applied_transfer_projects/organizations.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @organizations.size +json.organizations @organizations do |org| + json.partial! "/organizations/organizations/simple", locals: {organization: org} +end diff --git a/app/views/pull_requests/_merge_item.json.jbuilder b/app/views/pull_requests/_merge_item.json.jbuilder index 8475c468..0db17e26 100644 --- a/app/views/pull_requests/_merge_item.json.jbuilder +++ b/app/views/pull_requests/_merge_item.json.jbuilder @@ -5,10 +5,10 @@ json.issue_priories @project_priories json.project_author @project.owner.try(:show_real_name) json.project_name @project.try(:name) json.members do - json.array! @project_members.to_a.each do |member| - json.id member.user_id - json.login member.user.try(:login) - json.name member.user.try(:show_real_name) - json.avatar_url url_to_avatar(member.user) + json.array! @project_members.to_a.each do |user| + json.id user.id + json.login user.try(:login) + json.name user.try(:show_real_name) + json.avatar_url url_to_avatar(user) end end \ No newline at end of file diff --git a/app/views/repositories/detail.json.jbuilder b/app/views/repositories/detail.json.jbuilder index fc1dc830..75b474ee 100644 --- a/app/views/repositories/detail.json.jbuilder +++ b/app/views/repositories/detail.json.jbuilder @@ -21,7 +21,7 @@ json.versions_count @project.versions_count #里程碑数量 json.version_releases_count @project.releases_size(@user.try(:id), "all") json.version_releasesed_count @project.releases_size(@user.try(:id), "released") #已发行的版本 json.permission render_permission(@user, @project) -json.mirror_url @project&.repository.mirror_url +json.mirror_url @project&.repository.source_clone_url json.mirror @project&.repository.mirror_url.present? json.type @project.numerical_for_project_type json.open_devops @project.open_devops? @@ -80,7 +80,7 @@ json.contributors do total_count = @result[:contributor].size json.list @result[:contributor].each do |contributor| user = User.find_by(gitea_uid: contributor["id"]) - if contributor["login"] == "root" + if contributor["login"] == "root" || user.nil? total_count -= 1 next end diff --git a/app/views/repositories/edit.json.jbuilder b/app/views/repositories/edit.json.jbuilder index 076ac3aa..7a11411f 100644 --- a/app/views/repositories/edit.json.jbuilder +++ b/app/views/repositories/edit.json.jbuilder @@ -7,4 +7,9 @@ json.project_language_id @project.project_language_id json.private !@project.is_public json.website @project.website json.project_units @project.project_units.pluck(:unit_type) -json.lesson_url @project.lesson_url \ No newline at end of file +json.lesson_url @project.lesson_url +json.permission render_permission(current_user, @project) +json.is_transfering @project.is_transfering +json.transfer do + json.partial! "/users/user_simple", locals: {user: @project&.applied_transfer_project&.owner} +end \ No newline at end of file diff --git a/app/views/users/_user_simple.json.jbuilder b/app/views/users/_user_simple.json.jbuilder index 3f9feebb..5da16150 100644 --- a/app/views/users/_user_simple.json.jbuilder +++ b/app/views/users/_user_simple.json.jbuilder @@ -1,4 +1,9 @@ -json.id user.id -json.name user.real_name -json.login user.login -json.image_url url_to_avatar(user) \ No newline at end of file +if user.present? + json.id user.id + json.type user.type + json.name user.real_name + json.login user.login + json.image_url url_to_avatar(user) +else + json.nil! +end \ No newline at end of file diff --git a/app/views/users/applied_messages/_detail.json.jbuilder b/app/views/users/applied_messages/_detail.json.jbuilder new file mode 100644 index 00000000..cca202c1 --- /dev/null +++ b/app/views/users/applied_messages/_detail.json.jbuilder @@ -0,0 +1,26 @@ +# project = object.project +# json.project do +# json.id project.id +# json.identifier project.identifier +# json.name project.name +# json.description project.description +# json.is_public project.is_public +# json.owner do +# json.partial! "/users/user_simple", locals: {user: project.owner} +# end +# end +# json.user do +# json.partial! "/users/user_simple", locals: {user: object.user} +# end +json.applied do + json.partial! "/projects/applied_transfer_projects/detail", locals: {object: object.applied} +end +json.applied_user do + json.partial! "/users/user_simple", locals: {user: object.applied_user} +end +json.applied_type object.applied_type +json.name object.name +json.viewed object.viewed +json.status object.status +json.created_at format_time(object.created_at) +json.time_ago time_from_now(object.created_at) diff --git a/app/views/users/applied_messages/index.json.jbuilder b/app/views/users/applied_messages/index.json.jbuilder new file mode 100644 index 00000000..75647566 --- /dev/null +++ b/app/views/users/applied_messages/index.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @applied_messages.total_count +json.applied_messages @applied_messages do |message| + json.partial! "/users/applied_messages/detail", locals: {object: message} +end diff --git a/app/views/users/applied_transfer_projects/accept.json.jbuilder b/app/views/users/applied_transfer_projects/accept.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/users/applied_transfer_projects/accept.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/users/applied_transfer_projects/index.json.jbuilder b/app/views/users/applied_transfer_projects/index.json.jbuilder new file mode 100644 index 00000000..caf9d1ad --- /dev/null +++ b/app/views/users/applied_transfer_projects/index.json.jbuilder @@ -0,0 +1,4 @@ +json.total_count @applied_transfer_projects.total_count +json.applied_transfer_projects @applied_transfer_projects do |apply| + json.partial! "/projects/applied_transfer_projects/detail", locals: {object: apply} +end diff --git a/app/views/users/applied_transfer_projects/refuse.json.jbuilder b/app/views/users/applied_transfer_projects/refuse.json.jbuilder new file mode 100644 index 00000000..59da42d4 --- /dev/null +++ b/app/views/users/applied_transfer_projects/refuse.json.jbuilder @@ -0,0 +1 @@ +json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} diff --git a/app/views/users/show.json.jbuilder b/app/views/users/show.json.jbuilder index 54f85c5c..e80ac749 100644 --- a/app/views/users/show.json.jbuilder +++ b/app/views/users/show.json.jbuilder @@ -10,6 +10,8 @@ json.user_identity @user.identity json.is_watch current_user&.watched?(@user) json.watched_count @user.fan_count #粉丝 json.watching_count @user.follow_count #关注数 +json.undo_messages @waiting_applied_messages.size +json.undo_transfer_projects @common_applied_transfer_projects.size json.undo_events @undo_events json.user_composes_count @user_composes_count json.user_org_count @user_org_count diff --git a/config/configuration.yml.example b/config/configuration.yml.example index 823d8547..b45c9532 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -45,10 +45,16 @@ default: &default signature_key: 'test12345678' gitea: - access_key_id: 'root' - access_key_secret: '_Trustie_10010' - domain: 'https://testgitea.trustie.net' + access_key_id: '' + access_key_secret: '' + domain: 'https://testgit.trustie.net' base_url: '/api/v1' + accelerator: + access_key_id: '' + access_key_secret: '' + access_admin_uid: 1 + domain: 'https://testgit.trustie.net' + base_url: '/api/v1' production: diff --git a/config/gitea_response.yml b/config/gitea_response.yml new file mode 100644 index 00000000..1ff636ac --- /dev/null +++ b/config/gitea_response.yml @@ -0,0 +1,7 @@ +gitea: + pull_request: + merge_service: + 405: + default: "此合并请求有变更与目标分支冲突。" + 'User not allowed to merge PR': "用户没有合并请求的权限" + 403: diff --git a/config/locales/forms/create_issuse_form.zh-CN.yml b/config/locales/forms/create_issuse_form.zh-CN.yml new file mode 100644 index 00000000..643c6866 --- /dev/null +++ b/config/locales/forms/create_issuse_form.zh-CN.yml @@ -0,0 +1,7 @@ +'zh-CN': + activemodel: + attributes: + issues/create_form: + subject: 标题 + issues/update_form: + subject: 标题 \ No newline at end of file diff --git a/config/locales/forms/organizations_create_form.zh-CN.yml b/config/locales/forms/organizations_create_form.zh-CN.yml new file mode 100644 index 00000000..ab43b094 --- /dev/null +++ b/config/locales/forms/organizations_create_form.zh-CN.yml @@ -0,0 +1,9 @@ +'zh-CN': + activemodel: + attributes: + organizations/create_form: + name: 组织账号 + nickname: 组织名称 + location: 组织所在地区 + description: 组织简介 + visibility: 组织可见性 \ No newline at end of file diff --git a/config/locales/forms/organizations_create_team_form.zh-CN.yml b/config/locales/forms/organizations_create_team_form.zh-CN.yml new file mode 100644 index 00000000..ebf40dde --- /dev/null +++ b/config/locales/forms/organizations_create_team_form.zh-CN.yml @@ -0,0 +1,7 @@ +'zh-CN': + activemodel: + attributes: + organizations/create_team_form: + name: 团队标识 + nickname: 团队名称 + description: 团队描述 \ No newline at end of file diff --git a/config/locales/forms/projects_create_form.zh-CN.yml b/config/locales/forms/projects_create_form.zh-CN.yml new file mode 100644 index 00000000..e36a0520 --- /dev/null +++ b/config/locales/forms/projects_create_form.zh-CN.yml @@ -0,0 +1,7 @@ +'zh-CN': + activemodel: + attributes: + projects/create_form: + name: 项目名称 + repository_name: 仓库名称 + description: 项目简介 \ No newline at end of file diff --git a/config/locales/forms/projects_update_form.zh-CN.yml b/config/locales/forms/projects_update_form.zh-CN.yml new file mode 100644 index 00000000..74804f6a --- /dev/null +++ b/config/locales/forms/projects_update_form.zh-CN.yml @@ -0,0 +1,8 @@ +'zh-CN': + activemodel: + attributes: + projects/update_form: + name: 项目名称 + description: 项目简介 + project_category_id: 项目类别 + project_language_id: 项目语言 \ No newline at end of file diff --git a/config/locales/forms/users_login_form.zh-CN.yml b/config/locales/forms/users_login_form.zh-CN.yml new file mode 100644 index 00000000..8b94db42 --- /dev/null +++ b/config/locales/forms/users_login_form.zh-CN.yml @@ -0,0 +1,6 @@ +'zh-CN': + activemodel: + attributes: + users/login_form: + login: 用户名 + password: 密码 \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index bbcc2923..2901e0f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -257,6 +257,13 @@ Rails.application.routes.draw do end scope module: :users do + resources :applied_messages, only: [:index] + resources :applied_transfer_projects, only: [:index] do + member do + post :accept + post :refuse + end + end resources :organizations, only: [:index] # resources :projects, only: [:index] # resources :subjects, only: [:index] @@ -385,7 +392,6 @@ Rails.application.routes.draw do get :files get :detail get :archive - get :top_counts get :entries match :sub_entries, :via => [:get, :put] get :commits @@ -532,6 +538,12 @@ Rails.application.routes.draw do scope module: :projects do resources :teams, only: [:index, :create, :destroy] resources :project_units, only: [:index, :create] + resources :applied_transfer_projects, only: [:create] do + collection do + get :organizations + post :cancel + end + end scope do get( '/blob/*id/diff', diff --git a/db/migrate/20210425032825_create_applied_transfer_projects.rb b/db/migrate/20210425032825_create_applied_transfer_projects.rb new file mode 100644 index 00000000..5c677409 --- /dev/null +++ b/db/migrate/20210425032825_create_applied_transfer_projects.rb @@ -0,0 +1,12 @@ +class CreateAppliedTransferProjects < ActiveRecord::Migration[5.2] + def change + create_table :applied_transfer_projects do |t| + t.references :project + t.references :owner + t.references :user + t.integer :status, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20210429095016_add_accelerator_url_to_repositories.rb b/db/migrate/20210429095016_add_accelerator_url_to_repositories.rb new file mode 100644 index 00000000..0c33ef7b --- /dev/null +++ b/db/migrate/20210429095016_add_accelerator_url_to_repositories.rb @@ -0,0 +1,5 @@ +class AddAcceleratorUrlToRepositories < ActiveRecord::Migration[5.2] + def change + add_column :repositories, :accelerator_url, :string, default: "" + end +end diff --git a/db/migrate/20210429100619_add_source_clone_url_to_repositories.rb b/db/migrate/20210429100619_add_source_clone_url_to_repositories.rb new file mode 100644 index 00000000..a7f21303 --- /dev/null +++ b/db/migrate/20210429100619_add_source_clone_url_to_repositories.rb @@ -0,0 +1,5 @@ +class AddSourceCloneUrlToRepositories < ActiveRecord::Migration[5.2] + def change + add_column :repositories, :source_clone_url, :string, default: "" + end +end diff --git a/lib/tasks/sync_org_project_permission.rake b/lib/tasks/sync_org_project_permission.rake new file mode 100644 index 00000000..5828d1c8 --- /dev/null +++ b/lib/tasks/sync_org_project_permission.rake @@ -0,0 +1,8 @@ +namespace :sync_org_project_permission do + desc "sync organization project team permissions" + task mirror: :environment do + Project.mirror.includes(:team_projects,:owner).where(team_projects: {id: nil}, users: {type: 'Organization'}).find_each do |project| + project.set_owner_permission(nil) + end + end +end \ No newline at end of file diff --git a/public/docs/api.html b/public/docs/api.html index 1b75a97a..f970ea84 100644 --- a/public/docs/api.html +++ b/public/docs/api.html @@ -331,6 +331,18 @@
  • 获取当前登陆用户信息
  • +
  • + 待办事项-用户通知信息 +
  • +
  • + 待办事项-接受仓库 +
  • +
  • + 用户接受迁移 +
  • +
  • + 用户拒绝迁移 +
  • @@ -366,6 +378,15 @@
  • Fork项目
  • +
  • + 用户管理的组织列表 +
  • +
  • + 迁移项目 +
  • +
  • + 取消迁移项目 +
  • @@ -563,6 +584,12 @@ http://localhost:3000/api/ignores.json +

    Users

    获取当前登陆用户信息

    获取当前登陆用户信息

    @@ -622,7 +649,818 @@ Success — a happy kitten is an authenticated kitten! -

    Projects

    获取项目列表

    +

    待办事项-用户通知信息

    +

    待办事项-用户通知信息

    + +
    +

    示例:

    +
    +
    curl -X GET http://localhost:3000/api/users/yystopf/applied_messages.json
    +
    await octokit.request('GET /api/users/:login/applied_messages.json')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_messages.json

    +

    请求字段说明:

    + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    appliedobject通知主体
    applied.idint通知主体的迁移id
    applied.statusstring通知主体的迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    applied.time_agostring通知主体的迁移创建的时间
    applied.project.idint通知主体的迁移项目的id
    applied.project.identifierstring通知主体的迁移项目的标识
    applied.project.namestring通知主体的迁移项目的名称
    applied.project.descriptionstring通知主体的迁移项目的描述
    applied.project.is_publicbool通知主体的迁移项目是否公开
    applied.project.owner.idbool通知主体的迁移项目拥有者id
    applied.project.owner.typestring通知主体的迁移项目拥有者类型
    applied.project.owner.namestring通知主体的迁移项目拥有者昵称
    applied.project.owner.loginstring通知主体的迁移项目拥有者标识
    applied.project.owner.image_urlstring通知主体的迁移项目拥有者头像
    applied.user.idint通知主体的迁移创建者的id
    applied.user.typestring通知主体的迁移创建者的类型
    applied.user.namestring通知主体的迁移创建者的名称
    applied.user.loginstring通知主体的迁移创建者的标识
    applied.user.image_urlstring通知主体的迁移创建者头像
    applied.owner.idint通知主体的迁移接受者的id
    applied.owner.typestring通知主体的迁移接受者的类型
    applied.owner.namestring通知主体的迁移接受者的名称
    applied.owner.loginstring通知主体的迁移接受者的标识
    applied.owner.image_urlstring通知主体的迁移接受者头像
    applied_typestring通知类型
    namestring通知内容
    viewedstring是否已读,waiting:未读,viewed:已读
    statusstring通知状态, canceled:已取消,common: 正常,successed:成功,failure:失败
    time_agostring通知时间
    + +
    +

    返回的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')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_transfer_projects.json

    +

    请求字段说明:

    + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的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')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_transfer_projects/:id/accept.json

    +

    请求字段说明:

    + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    idint迁移id
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的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')
    +

    HTTP 请求

    +

    GET /api/users/:login/applied_transfer_projects/:id/refuse.json

    +

    请求字段说明:

    + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    loginstring用户标识
    idint迁移id
    +

    返回字段说明:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的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小时前"
    +}
    +

    Projects

    获取项目列表

    获取项目列表,也可以更加相关条件过滤搜素

    @@ -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

    +

    请求参数

    + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    +

    返回字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    namestring组织标识
    nicknamestring组织名称
    descriptionstring组织描述
    avatar_urlstring|组织头像
    + +
    +

    返回的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

    +

    请求参数

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    owner_namestring迁移对象标识
    +

    返回字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint项目id
    statusstring项目迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring项目迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的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

    +

    请求参数

    + + + + + + + + + + + + + + + + + + + + + + + +
    参数必选默认类型字段说明
    ownerstring用户登录名
    repostring项目标识identifier
    +

    返回字段说明

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数类型字段说明
    idint迁移id
    statusstring迁移状态,canceled:取消,common:正在迁移, accept:已接受,refuse:已拒绝
    time_agostring迁移创建的时间
    project.idint迁移项目的id
    project.identifierstring迁移项目的标识
    project.namestring迁移项目的名称
    project.descriptionstring迁移项目的描述
    project.is_publicbool迁移项目是否公开
    project.owner.idbool迁移项目拥有者id
    project.owner.typestring迁移项目拥有者类型
    project.owner.namestring迁移项目拥有者昵称
    project.owner.loginstring迁移项目拥有者标识
    project.owner.image_urlstring迁移项目拥有者头像
    user.idint迁移创建者的id
    user.typestring迁移创建者的类型
    user.namestring迁移创建者的名称
    user.loginstring迁移创建者的标识
    user.image_urlstring迁移创建者头像
    owner.idint迁移接受者的id
    owner.typestring迁移接受者的类型
    owner.namestring迁移接受者的名称
    owner.loginstring迁移接受者的标识
    owner.image_urlstring迁移接受者头像
    + +
    +

    返回的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

    仓库详情

    仓库详情