| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| 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) | |||
| 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[: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 | |||
| 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[: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 | |||
| @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, "创建失败") | |||
| #为悬赏任务时, 扣除当前用户的积分 | |||
| 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 | |||
| @@ -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 | |||
| @@ -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? | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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, | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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( | |||
| @@ -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 | |||
| @@ -23,7 +23,7 @@ | |||
| <td><%= list_index_no((params[:page] || 1).to_i, index) %></td> | |||
| <td><%= project.id %></td> | |||
| <td class="text-left"> | |||
| <%= link_to(project.name, "/projects/#{project.id}", target: '_blank') %> | |||
| <%= link_to(project.name, "/projects/#{project&.owner&.login}/#{project.identifier}", target: '_blank') %> | |||
| </td> | |||
| <td><%= project.is_public ? '√' : '' %></td> | |||
| <td><%= project.issues.size %></td> | |||
| @@ -33,7 +33,7 @@ | |||
| <td><%= project.versions.size %></td> | |||
| <td><%= project.members.size %></td> | |||
| <td> | |||
| <%= project.owner ? link_to(project.owner&.real_name, "/users/#{project.owner&.login}", target: '_blank') : "" %> | |||
| <%= link_to_project(project) %> | |||
| </td> | |||
| <td><%= project.created_on&.strftime('%Y-%m-%d %H:%M') %></td> | |||
| <td class="action-container"> | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -0,0 +1,4 @@ | |||
| json.total_count @organizations.size | |||
| json.organizations @organizations do |org| | |||
| json.partial! "/organizations/organizations/simple", locals: {organization: org} | |||
| end | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| 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 | |||
| @@ -1,4 +1,9 @@ | |||
| json.id user.id | |||
| json.name user.real_name | |||
| json.login user.login | |||
| json.image_url url_to_avatar(user) | |||
| 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 | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -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 | |||
| @@ -0,0 +1 @@ | |||
| json.partial! "/projects/applied_transfer_projects/detail", locals: {object: @applied_transfer_project} | |||
| @@ -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 | |||
| @@ -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: | |||
| @@ -0,0 +1,7 @@ | |||
| gitea: | |||
| pull_request: | |||
| merge_service: | |||
| 405: | |||
| default: "此合并请求有变更与目标分支冲突。" | |||
| 'User not allowed to merge PR': "用户没有合并请求的权限" | |||
| 403: | |||
| @@ -0,0 +1,7 @@ | |||
| 'zh-CN': | |||
| activemodel: | |||
| attributes: | |||
| issues/create_form: | |||
| subject: 标题 | |||
| issues/update_form: | |||
| subject: 标题 | |||
| @@ -0,0 +1,9 @@ | |||
| 'zh-CN': | |||
| activemodel: | |||
| attributes: | |||
| organizations/create_form: | |||
| name: 组织账号 | |||
| nickname: 组织名称 | |||
| location: 组织所在地区 | |||
| description: 组织简介 | |||
| visibility: 组织可见性 | |||
| @@ -0,0 +1,7 @@ | |||
| 'zh-CN': | |||
| activemodel: | |||
| attributes: | |||
| organizations/create_team_form: | |||
| name: 团队标识 | |||
| nickname: 团队名称 | |||
| description: 团队描述 | |||
| @@ -0,0 +1,7 @@ | |||
| 'zh-CN': | |||
| activemodel: | |||
| attributes: | |||
| projects/create_form: | |||
| name: 项目名称 | |||
| repository_name: 仓库名称 | |||
| description: 项目简介 | |||
| @@ -0,0 +1,8 @@ | |||
| 'zh-CN': | |||
| activemodel: | |||
| attributes: | |||
| projects/update_form: | |||
| name: 项目名称 | |||
| description: 项目简介 | |||
| project_category_id: 项目类别 | |||
| project_language_id: 项目语言 | |||
| @@ -0,0 +1,6 @@ | |||
| 'zh-CN': | |||
| activemodel: | |||
| attributes: | |||
| users/login_form: | |||
| login: 用户名 | |||
| password: 密码 | |||
| @@ -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', | |||
| @@ -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 | |||
| @@ -0,0 +1,5 @@ | |||
| class AddAcceleratorUrlToRepositories < ActiveRecord::Migration[5.2] | |||
| def change | |||
| add_column :repositories, :accelerator_url, :string, default: "" | |||
| end | |||
| end | |||
| @@ -0,0 +1,5 @@ | |||
| class AddSourceCloneUrlToRepositories < ActiveRecord::Migration[5.2] | |||
| def change | |||
| add_column :repositories, :source_clone_url, :string, default: "" | |||
| end | |||
| end | |||
| @@ -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 | |||