From bd16ca476d8d84e868b778c2a0f0f9a95dbe8a10 Mon Sep 17 00:00:00 2001 From: yystopf Date: Thu, 26 May 2022 15:08:42 +0800 Subject: [PATCH 001/242] =?UTF-8?q?=E5=A2=9E=E5=8A=A0:=20=E9=80=80?= =?UTF-8?q?=E5=87=BA=E4=BB=93=E5=BA=93api=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/docs/slate/source/includes/_projects.md | 32 ++++++++++++++ public/docs/api.html | 46 +++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/app/docs/slate/source/includes/_projects.md b/app/docs/slate/source/includes/_projects.md index d4899a71..e133bcdd 100644 --- a/app/docs/slate/source/includes/_projects.md +++ b/app/docs/slate/source/includes/_projects.md @@ -849,4 +849,36 @@ await octokit.request('POST /api/:owner/:repo/applied_transfer_projects/cancel.j "created_at": "2021-04-26 09:54", "time_ago": "1分钟前" } +``` + +## 退出项目 +供项目成员(开发者、报告者)退出项目用 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json +``` + +```javascript +await octokit.request('POST /api/:owner/:repo/quit.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/quit.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|owner |是| |string |用户登录名 | +|repo |是| |string |项目标识identifier | + + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} ``` \ No newline at end of file diff --git a/public/docs/api.html b/public/docs/api.html index 45da6440..8ef6924b 100644 --- a/public/docs/api.html +++ b/public/docs/api.html @@ -467,6 +467,9 @@
  • 取消迁移项目
  • +
  • + 退出项目 +
  • @@ -6026,6 +6029,49 @@ http://localhost:3000/api/ceshi1/ceshi_repo1/applied_transfer_projects/organizat "created_at": "2021-04-26 09:54", "time_ago": "1分钟前" } +

    退出项目

    +

    供项目成员(开发者、报告者)退出项目用

    + +
    +

    示例:

    +
    +
    curl -X POST http://localhost:3000/api/ceshi1/ceshi_repo1/quit.json
    +
    await octokit.request('POST /api/:owner/:repo/quit.json')
    +

    HTTP 请求

    +

    POST /api/:owner/:repo/quit.json

    +

    请求参数

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

    返回的JSON示例:

    +
    +
    {
    +    "status": 0,
    +    "message": "success"
    +}
     

    Repositories

    仓库详情

    仓库详情

    From a6e50d2a250168a0cb981d403a6a4ec65a37ea5b Mon Sep 17 00:00:00 2001 From: yystopf Date: Fri, 27 May 2022 10:48:20 +0800 Subject: [PATCH 002/242] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20devops=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=BF=80=E6=B4=BB=E9=A1=B9=E7=9B=AE=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/ci/projects_controller.rb | 2 +- .../concerns/ci/cloud_account_manageable.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index 69644e7a..06e51537 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -40,9 +40,9 @@ class Ci::ProjectsController < Ci::BaseController begin ActiveRecord::Base.transaction do if @repo + @repo.destroy! if @repo&.repo_user_id == 0 return render_error('该项目已经激活') if @repo.repo_active? @repo.activate!(@project) - return render_ok else @repo = Ci::Repo.auto_create!(@ci_user, @project) @ci_user.update_column(:user_syncing, false) diff --git a/app/controllers/concerns/ci/cloud_account_manageable.rb b/app/controllers/concerns/ci/cloud_account_manageable.rb index 9f0998ed..f2157657 100644 --- a/app/controllers/concerns/ci/cloud_account_manageable.rb +++ b/app/controllers/concerns/ci/cloud_account_manageable.rb @@ -160,9 +160,9 @@ module Ci::CloudAccountManageable state = SecureRandom.hex(8) # redirect_uri eg: # https://localhost:3000/login/oauth/authorize?client_id=94976481-ad0e-4ed4-9247-7eef106007a2&redirect_uri=http%3A%2F%2F121.69.81.11%3A80%2Flogin&response_type=code&state=9cab990b9cfb1805 - redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login") - clientId = client_id(oauth) - grant_url = "#{Gitea.gitea_config[:domain]}/login/oauth/authorize?client_id=#{clientId}&redirect_uri=#{redirect_uri}&response_type=code&state=#{state}" + # redirect_uri = CGI.escape("#{@cloud_account.drone_url}/login") + # clientId = client_id(oauth) + grant_url = "#{@cloud_account.drone_url}/login" logger.info "[gitea] grant_url: #{grant_url}" conn = Faraday.new(url: grant_url) do |req| @@ -179,7 +179,7 @@ module Ci::CloudAccountManageable def drone_oauth_user!(url, state) logger.info "[drone] drone_oauth_user url: #{url}" - conn = Faraday.new(url: "#{Gitea.gitea_config[:domain]}#{url}") do |req| + conn = Faraday.new(url: url) do |req| req.request :url_encoded req.adapter Faraday.default_adapter req.headers["cookie"] = "_session_=#{SecureRandom.hex(28)}; _oauth_state_=#{state}" @@ -188,8 +188,8 @@ module Ci::CloudAccountManageable response = conn.get logger.info "[drone] response headers: #{response.headers}" - true - # response.headers['location'].include?('error') ? false : true + # true + response.headers['location'].include?('error') ? false : true end private From 17fb43cea83ca814ed5c66add62de41d7b1fe2ab Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Mon, 30 May 2022 10:24:39 +0800 Subject: [PATCH 003/242] =?UTF-8?q?render=5Ferror=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E6=88=90tip=5Fexception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/ci/projects_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index 06e51537..c358611e 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -41,7 +41,7 @@ class Ci::ProjectsController < Ci::BaseController ActiveRecord::Base.transaction do if @repo @repo.destroy! if @repo&.repo_user_id == 0 - return render_error('该项目已经激活') if @repo.repo_active? + return tip_exception('该项目已经激活') if @repo.repo_active? @repo.activate!(@project) else @repo = Ci::Repo.auto_create!(@ci_user, @project) @@ -55,7 +55,7 @@ class Ci::ProjectsController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end end From 6448a30e0301ed24eea251f1f766d1755f8991eb Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Mon, 30 May 2022 10:25:43 +0800 Subject: [PATCH 004/242] =?UTF-8?q?render=5Ferror=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E6=88=90tip=5Fexception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ci/cloud_accounts_controller.rb | 34 +++++++++---------- app/controllers/ci/pipelines_controller.rb | 24 ++++++------- app/controllers/ci/projects_controller.rb | 6 ++-- app/controllers/ci/secrets_controller.rb | 6 ++-- app/controllers/ci/templates_controller.rb | 6 ++-- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/app/controllers/ci/cloud_accounts_controller.rb b/app/controllers/ci/cloud_accounts_controller.rb index 7d829dbd..d22b2ba4 100644 --- a/app/controllers/ci/cloud_accounts_controller.rb +++ b/app/controllers/ci/cloud_accounts_controller.rb @@ -14,12 +14,12 @@ class Ci::CloudAccountsController < Ci::BaseController def create flag, msg = check_bind_cloud_account! - return render_error(msg) if flag === true + return tip_exception(msg) if flag === true ActiveRecord::Base.transaction do @cloud_account = bind_account! if @cloud_account.blank? - render_error('激活失败, 请检查你的云服务器信息是否正确.') + tip_exception('激活失败, 请检查你的云服务器信息是否正确.') raise ActiveRecord::Rollback else current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) @@ -27,17 +27,17 @@ class Ci::CloudAccountsController < Ci::BaseController end end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def activate - return render_error('请先认证') unless current_user.ci_certification? + return tip_exception('请先认证') unless current_user.ci_certification? begin @cloud_account = Ci::CloudAccount.find params[:id] ActiveRecord::Base.transaction do if @repo - return render_error('该项目已经激活') if @repo.repo_active? + return tip_exception('该项目已经激活') if @repo.repo_active? @repo.activate!(@project) else @repo = Ci::Repo.auto_create!(@ci_user, @project) @@ -50,7 +50,7 @@ class Ci::CloudAccountsController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end end @@ -59,39 +59,39 @@ class Ci::CloudAccountsController < Ci::BaseController def bind flag, msg = check_bind_cloud_account! - return render_error(msg) if flag === true + return tip_exception(msg) if flag === true ActiveRecord::Base.transaction do @cloud_account = bind_account! if @cloud_account.blank? - render_error('激活失败, 请检查你的云服务器信息是否正确.') + tip_exception('激活失败, 请检查你的云服务器信息是否正确.') raise ActiveRecord::Rollback else current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) end end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def trustie_bind account = params[:account].to_s - return render_error("account不能为空.") if account.blank? + return tip_exception("account不能为空.") if account.blank? flag, msg = check_trustie_bind_cloud_account! - return render_error(msg) if flag === true + return tip_exception(msg) if flag === true ActiveRecord::Base.transaction do @cloud_account = trustie_bind_account! if @cloud_account.blank? - render_error('激活失败, 请检查你的云服务器信息是否正确.') + tip_exception('激活失败, 请检查你的云服务器信息是否正确.') raise ActiveRecord::Rollback else current_user.set_drone_step!(User::DEVOPS_UNVERIFIED) end end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def unbind @@ -107,18 +107,18 @@ class Ci::CloudAccountsController < Ci::BaseController render_ok end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def oauth_grant password = params[:password].to_s - return render_error('你输入的密码不正确.') unless current_user.check_password?(password) + return tip_exception('你输入的密码不正确.') unless current_user.check_password?(password) oauth = current_user.oauths.last - return render_error("服务器出小差了.") if oauth.blank? + return tip_exception("服务器出小差了.") if oauth.blank? result = gitea_oauth_grant!(password, oauth) - return render_error('授权失败.') unless result === true + return tip_exception('授权失败.') unless result === true current_user.set_drone_step!(User::DEVOPS_CERTIFICATION) end diff --git a/app/controllers/ci/pipelines_controller.rb b/app/controllers/ci/pipelines_controller.rb index ea8b25f7..28805965 100644 --- a/app/controllers/ci/pipelines_controller.rb +++ b/app/controllers/ci/pipelines_controller.rb @@ -30,7 +30,7 @@ class Ci::PipelinesController < Ci::BaseController ActiveRecord::Base.transaction do size = Ci::Pipeline.where('branch=? and identifier=? and owner=?', params[:branch], params[:repo], params[:owner]).size if size > 0 - render_error("#{params[:branch]}分支已经存在流水线!") + tip_exception("#{params[:branch]}分支已经存在流水线!") return end pipeline = Ci::Pipeline.new(pipeline_name: params[:pipeline_name], file_name: params[:file_name],owner: params[:owner], @@ -53,7 +53,7 @@ class Ci::PipelinesController < Ci::BaseController render_ok({id: pipeline.id}) end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end # 在代码库创建文件 @@ -118,7 +118,7 @@ class Ci::PipelinesController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def destroy @@ -132,7 +132,7 @@ class Ci::PipelinesController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def content @@ -182,7 +182,7 @@ class Ci::PipelinesController < Ci::BaseController render_ok end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def update_stage @@ -192,7 +192,7 @@ class Ci::PipelinesController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def delete_stage @@ -205,7 +205,7 @@ class Ci::PipelinesController < Ci::BaseController render_ok end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def update_stage_index(pipeline_id, show_index, diff) @@ -229,7 +229,7 @@ class Ci::PipelinesController < Ci::BaseController unless steps.empty? steps.each do |step| unless step[:template_id] - render_error('请选择模板!') + tip_exception('请选择模板!') return end if !step[:id] @@ -246,7 +246,7 @@ class Ci::PipelinesController < Ci::BaseController render_ok end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def create_stage_step @@ -262,7 +262,7 @@ class Ci::PipelinesController < Ci::BaseController render_ok end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def update_stage_step @@ -279,7 +279,7 @@ class Ci::PipelinesController < Ci::BaseController render_ok end rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def delete_stage_step @@ -289,6 +289,6 @@ class Ci::PipelinesController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end end diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index c358611e..53f11fb2 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -30,12 +30,12 @@ class Ci::ProjectsController < Ci::BaseController @file = interactor.result render_result(1, "更新成功") else - render_error(interactor.error) + tip_exception(interactor.error) end end def activate - return render_error('你还未认证') unless current_user.ci_certification? + return tip_exception('你还未认证') unless current_user.ci_certification? begin ActiveRecord::Base.transaction do @@ -60,7 +60,7 @@ class Ci::ProjectsController < Ci::BaseController end def deactivate - return render_error('该项目已经取消激活') if !@repo.repo_active? + return tip_exception('该项目已经取消激活') if !@repo.repo_active? @project.update_column(:open_devops, false) @repo.deactivate_repos! diff --git a/app/controllers/ci/secrets_controller.rb b/app/controllers/ci/secrets_controller.rb index 324f0bb5..3057824e 100644 --- a/app/controllers/ci/secrets_controller.rb +++ b/app/controllers/ci/secrets_controller.rb @@ -20,14 +20,14 @@ class Ci::SecretsController < Ci::BaseController if result["id"] render_ok else - render_error(result["message"]) + tip_exception(result["message"]) end else result = Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], options).create_secret if result["id"] render_ok else - render_error(result["message"]) + tip_exception(result["message"]) end end end @@ -39,7 +39,7 @@ class Ci::SecretsController < Ci::BaseController Ci::Drone::API.new(@ci_user.user_hash, ci_drone_url, params[:owner], params[:repo], {name: name}).delete_secret render_ok else - render_error("参数名不能为空") + tip_exception("参数名不能为空") end rescue Exception => ex render_ok diff --git a/app/controllers/ci/templates_controller.rb b/app/controllers/ci/templates_controller.rb index cb49590d..22c20952 100644 --- a/app/controllers/ci/templates_controller.rb +++ b/app/controllers/ci/templates_controller.rb @@ -50,7 +50,7 @@ class Ci::TemplatesController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def update @@ -63,7 +63,7 @@ class Ci::TemplatesController < Ci::BaseController ) render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end def destroy @@ -73,7 +73,7 @@ class Ci::TemplatesController < Ci::BaseController end render_ok rescue Exception => ex - render_error(ex.message) + tip_exception(ex.message) end #======流水线模板查询=====# From d6fd85f536f2b328faae42572da3337a4aa4417f Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Mon, 30 May 2022 11:18:24 +0800 Subject: [PATCH 005/242] =?UTF-8?q?Repository.url=E9=87=8D=E5=86=99?= =?UTF-8?q?=EF=BC=8C=E5=AF=BC=E5=85=A5=E9=A1=B9=E7=9B=AE=E6=97=B6=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/ci/pipelines_controller.rb | 1 + app/models/repository.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/controllers/ci/pipelines_controller.rb b/app/controllers/ci/pipelines_controller.rb index 28805965..d6eee138 100644 --- a/app/controllers/ci/pipelines_controller.rb +++ b/app/controllers/ci/pipelines_controller.rb @@ -81,6 +81,7 @@ class Ci::PipelinesController < Ci::BaseController repo_branch: pipeline.branch, repo_config: pipeline.file_name } + Rails.logger.info("########create_params===#{create_params.to_json}") repo = Ci::Repo.create_repo(create_params) repo end diff --git a/app/models/repository.rb b/app/models/repository.rb index a012b449..12cc2fa3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -48,6 +48,10 @@ class Repository < ApplicationRecord self.identifier.parameterize end + def url + self['url'].blank? ? "#{Rails.application.config_for(:configuration)['platform_url']}/#{self.owner&.login}/#{self.identifier}.git" : self['url'] + end + # with repository is mirror def set_mirror! self.build_mirror(status: Mirror.statuses[:waiting]).save From 169dc84e22f933e9d0b5d1e40fecc4a0ac8dacd4 Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 30 May 2022 14:50:12 +0800 Subject: [PATCH 006/242] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E4=B8=8B=E9=A1=B9=E7=9B=AE=E9=9C=80=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E4=B8=BA=E5=85=AC=E6=9C=89=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/organizations/projects/index.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/organizations/projects/index.json.jbuilder b/app/views/organizations/projects/index.json.jbuilder index a663b0c1..5a4615fc 100644 --- a/app/views/organizations/projects/index.json.jbuilder +++ b/app/views/organizations/projects/index.json.jbuilder @@ -1,6 +1,6 @@ json.total_count @projects.total_count json.projects @projects.each do |project| - json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id) + json.(project, :id, :name, :identifier, :description, :forked_count, :praises_count, :forked_from_project_id, :is_public) json.mirror_url project.repository&.mirror_url json.type project.numerical_for_project_type json.praised project.praised_by?(current_user) From 9427db3e5a9c1856e8519506fada75c7dc68248b Mon Sep 17 00:00:00 2001 From: yystopf Date: Tue, 31 May 2022 16:34:08 +0800 Subject: [PATCH 007/242] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E6=B6=88=E6=81=AF=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/notices_controller.rb | 14 ++++++- app/jobs/send_template_message_job.rb | 11 +++++ app/models/message_template/custom_tip.rb | 51 +++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 app/models/message_template/custom_tip.rb diff --git a/app/controllers/notices_controller.rb b/app/controllers/notices_controller.rb index bf7eb21b..7b67412e 100644 --- a/app/controllers/notices_controller.rb +++ b/app/controllers/notices_controller.rb @@ -1,7 +1,7 @@ class NoticesController < ApplicationController def create - tip_exception("参数有误") if params["source"].blank? + return tip_exception("参数有误") if params["source"].blank? user_id = params[:user_id] if params["source"] == "CompetitionBegin" @@ -13,9 +13,21 @@ class NoticesController < ApplicationController elsif params["source"] == "CompetitionReview" competition_id = params[:competition_id] SendTemplateMessageJob.perform_later('CompetitionReview', user_id, competition_id) + elsif params["source"] == "CustomTip" + users_id = params[:users_id] + props = params[:props].to_unsafe_hash + return tip_exception("参数有误") unless props.is_a?(Hash) && users_id.is_a?(Array) + template_id = params[:template_id] + SendTemplateMessageJob.perform_later('CustomTip', users_id, template_id, props) else tip_exception("#{params["source"]}未配置") end render_ok end + + + private + def params_props + params.require(:notice).permit(:props) + end end diff --git a/app/jobs/send_template_message_job.rb b/app/jobs/send_template_message_job.rb index 9cc819a1..bec3f7b0 100644 --- a/app/jobs/send_template_message_job.rb +++ b/app/jobs/send_template_message_job.rb @@ -4,6 +4,17 @@ class SendTemplateMessageJob < ApplicationJob def perform(source, *args) Rails.logger.info "SendTemplateMessageJob [args] #{args}" case source + when 'CustomTip' + receivers_id, template_id, props = args[0], args[1], args[2] + template = MessageTemplate.find_by_id(template_id) + return unless template.present? + receivers = User.where(id: receivers_id) + receivers_string, content, notification_url = MessageTemplate::CustomTip.get_message_content(receivers, template, props) + Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {receivers_id: receivers_id, template_id: template_id, props: props}) + receivers.find_each do |receiver| + receivers_email_string, email_title, email_content = MessageTemplate::CustomTip.get_email_message_content(receiver, template, props) + Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) + end when 'FollowTip' watcher_id = args[0] watcher = Watcher.find_by_id(watcher_id) diff --git a/app/models/message_template/custom_tip.rb b/app/models/message_template/custom_tip.rb new file mode 100644 index 00000000..6c5a3345 --- /dev/null +++ b/app/models/message_template/custom_tip.rb @@ -0,0 +1,51 @@ +# == Schema Information +# +# Table name: message_templates +# +# id :integer not null, primary key +# type :string(255) +# sys_notice :text(65535) +# email :text(65535) +# created_at :datetime not null +# updated_at :datetime not null +# notification_url :string(255) +# email_title :string(255) +# + +# 统一模板( + +class MessageTemplate::CustomTip < MessageTemplate + + # MessageTemplate::CustomTip.get_message_content(User.where(login: 'yystopf'), "hahah") + def self.get_message_content(receivers, template, props={}) + return '', '', '' if receivers.blank? || template.blank? + content = template.sys_notice + notification_url = template.notification_url + props.each do |k, v| + content.gsub!("{#{k}}", v) + notification_url.gsub!("{#{k}}", v) + end + return receivers_string(receivers), content, notification_url + rescue => e + Rails.logger.info("MessageTemplate::CustomTip.get_message_content [ERROR] #{e}") + return '', '', '' + end + + def self.get_email_message_content(receiver, template, props={}) + return '', '', '' if receiver.blank? || template.blank? + title = template.email_title + content = template.email + props.each do |k, v| + title.gsub!("{#{k}}", v) + content.gsub!("{#{k}}", v) + end + content.gsub!('{receiver}', receiver&.real_name) + title.gsub!('{platform}', PLATFORM) + content.gsub!('{platform}', PLATFORM) + + return receiver&.mail, title, content + rescue => e + Rails.logger.info("MessageTemplate::CustomTip.get_email_message_content [ERROR] #{e}") + return '', '', '' + end +end \ No newline at end of file From 9a17c4865f4188cd5d266caf3ea2d08d4e185532 Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Wed, 1 Jun 2022 09:27:18 +0800 Subject: [PATCH 008/242] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E5=92=8C=E6=8F=8F=E8=BF=B0=E5=A2=9E=E5=8A=A0=E6=95=8F=E6=84=9F?= =?UTF-8?q?=E8=AF=8D=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/project.rb | 839 +++++++++++++++++++++--------------------- 1 file changed, 423 insertions(+), 416 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 2e47c000..9c77685f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,416 +1,423 @@ - -# == Schema Information -# -# Table name: projects -# -# id :integer not null, primary key -# name :string(255) default(""), not null -# description :text(4294967295) -# homepage :string(255) default("") -# is_public :boolean default("1"), not null -# parent_id :integer -# created_on :datetime -# updated_on :datetime -# identifier :string(255) -# status :integer default("1"), not null -# lft :integer -# rgt :integer -# inherit_members :boolean default("0"), not null -# project_type :integer default("0") -# hidden_repo :boolean default("0"), not null -# attachmenttype :integer default("1") -# user_id :integer -# dts_test :integer default("0") -# enterprise_name :string(255) -# organization_id :integer -# project_new_type :integer -# gpid :integer -# forked_from_project_id :integer -# forked_count :integer default("0") -# publish_resource :integer default("0") -# visits :integer default("0") -# hot :integer default("0") -# invite_code :string(255) -# qrcode :string(255) -# qrcode_expiretime :integer default("0") -# script :text(65535) -# training_status :integer default("0") -# rep_identifier :string(255) -# project_category_id :integer -# project_language_id :integer -# license_id :integer -# ignore_id :integer -# praises_count :integer default("0") -# watchers_count :integer default("0") -# issues_count :integer default("0") -# pull_requests_count :integer default("0") -# language :string(255) -# versions_count :integer default("0") -# issue_tags_count :integer default("0") -# closed_issues_count :integer default("0") -# open_devops :boolean default("0") -# gitea_webhook_id :integer -# open_devops_count :integer default("0") -# recommend :boolean default("0") -# platform :integer default("0") -# default_branch :string(255) default("master") -# website :string(255) -# lesson_url :string(255) -# is_pinned :boolean default("0") -# recommend_index :integer default("0") -# -# Indexes -# -# index_projects_on_forked_from_project_id (forked_from_project_id) -# index_projects_on_identifier (identifier) -# index_projects_on_invite_code (invite_code) -# index_projects_on_is_public (is_public) -# index_projects_on_lft (lft) -# index_projects_on_license_id (license_id) -# index_projects_on_name (name) -# index_projects_on_platform (platform) -# index_projects_on_project_category_id (project_category_id) -# index_projects_on_project_language_id (project_language_id) -# index_projects_on_project_type (project_type) -# index_projects_on_recommend (recommend) -# index_projects_on_rgt (rgt) -# index_projects_on_status (status) -# index_projects_on_updated_on (updated_on) -# - - - - -class Project < ApplicationRecord - include Matchable - include Publicable - include Watchable - include ProjectOperable - include Dcodes - - # common:开源托管项目 - # mirror:普通镜像项目,没有定时同步功能 - # sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作 - # - enum project_type: { sync_mirror: 2, mirror: 1, common: 0 } - - # forge: trustie平台项目, educoder: educoder平台项目, 默认为forge平台 - enum platform: { forge: 0, educoder: 1 } - - belongs_to :ignore, optional: true - belongs_to :license, optional: true - belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, optional: true - belongs_to :organization_extension, foreign_key: :user_id, primary_key: :organization_id, optional: true, counter_cache: :num_projects - belongs_to :project_category, optional: true , :counter_cache => true - belongs_to :project_language, optional: true , :counter_cache => true - belongs_to :forked_from_project, class_name: 'Project', optional: true, foreign_key: :forked_from_project_id - has_many :project_trends, dependent: :destroy - has_many :watchers, as: :watchable, dependent: :destroy - has_many :fork_users, dependent: :destroy - has_many :forked_users, class_name: 'ForkUser', foreign_key: :fork_project_id, dependent: :destroy - has_many :forked_projects, class_name: 'Project', foreign_key: :forked_from_project_id - has_one :project_educoder, dependent: :destroy - - has_one :project_score, dependent: :destroy - has_one :repository, dependent: :destroy - has_many :pull_requests, dependent: :destroy - has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy - has_many :issues, dependent: :destroy - # has_many :user_grades, dependent: :destroy - has_many :attachments, as: :container, dependent: :destroy - has_one :project_score, dependent: :destroy - has_many :versions, -> { order("versions.created_on DESC, versions.name DESC") }, dependent: :destroy - has_many :praise_treads, as: :praise_tread_object, dependent: :destroy - has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" - has_one :project_detail, dependent: :destroy - has_many :project_units, dependent: :destroy - has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy - has_many :pinned_projects, dependent: :destroy - has_many :has_pinned_users, through: :pinned_projects, source: :user - has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id - after_create :incre_user_statistic, :incre_platform_statistic - after_save :check_project_members - before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data - before_destroy :decre_project_common, :decre_forked_from_project_count - after_destroy :decre_user_statistic, :decre_platform_statistic - scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)} - scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)} - scope :recommend, -> { visible.project_statics_select.where(recommend: true) } - scope :pinned, -> {where(is_pinned: true)} - - delegate :content, to: :project_detail, allow_nil: true - delegate :name, to: :license, prefix: true, allow_nil: true - - def self.all_visible(user_id=nil) - user_projects_sql = Project.joins(:owner).where(users: {type: 'User'}).to_sql - org_public_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'common'})).to_sql - if user_id.present? - org_limit_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'limited'})).to_sql - org_privacy_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension, :organization_users).where(organization_extensions: {visibility: 'privacy'}, organization_users: {user_id: user_id})).to_sql - return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } UNION #{ org_limit_projects_sql } UNION #{org_privacy_projects_sql} ) AS projects").visible - else - return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } ) AS projects").visible - end - end - - def reset_cache_data - CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id) - if changes[:user_id].present? - CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first) - CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last) - end - if changes[:project_language_id].present? - first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first) - last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last) - CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id) - CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id) - CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}) - CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}) - end - if changes[:is_public].present? - if changes[:is_public][0] && !changes[:is_public][1] - CacheAsyncClearJob.perform_later('project_rank_service', self.id) - end - if !changes[:is_public][0] && changes[:is_public][1] - $redis_cache.srem("v2-project-rank-deleted", self.id) - end - end - end - - def decre_project_common - CacheAsyncClearJob.perform_later('project_common_service', self.id) - end - - def decre_forked_from_project_count - forked_project = self.forked_from_project - if forked_project.present? - forked_project.decrement(:forked_count, 1) - forked_project.save - end - end - - def incre_user_statistic - CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}, self.user_id) - end - - def decre_user_statistic - CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}, self.user_id) - end - - def incre_platform_statistic - CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}) - end - - def decre_platform_statistic - CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}) - end - - def is_full_public - owner = self.owner - if owner.is_a?(Organization) - return self.is_public && owner&.visibility == "common" - else - return self.is_public - end - end - - def reset_unmember_followed - if changes[:is_public].present? && changes[:is_public] == [true, false] - self.watchers.where.not(user_id: self.all_collaborators).destroy_all - end - end - - def set_invite_code - if self.invite_code.nil? - self.invite_code= self.generate_dcode('invite_code', 6) - end - end - - def set_recommend_and_is_pinned - self.recommend = self.recommend_index.zero? ? false : true - # 私有项目不允许设置精选和推荐 - unless self.is_public - self.recommend = false - self.recommend_index = 0 - self.is_pinned = false - end - end - - def self.search_project(search) - ransack(name_or_identifier_cont: search) - end - # 创建者 - def creator - User.find(user_id).full_name - end - - def members_user_infos - members.joins(:roles).where("roles.name in ('Manager', 'Developer', 'Reporter')").joins("left join users on members.user_id = users.id ").includes(:user).where("users.type = ?", "User") - # members.joins("left join users on members.user_id = users.id").select("users.id", "users.login","users.firstname","users.lastname") - # .pluck("users.id", "users.login","users.lastname", "users.firstname") - end - - def to_param - self.identifier.parameterize - end - - def get_issues_count(status_id) - if status_id.present? - self&.issues.issue_issue.select(:id, :status_id).where(status_id: status_id)&.pluck(:id).size - else - self&.issues.issue_issue.select(:id)&.pluck(:id).size - end - end - - def get_pull_requests_count(status_id) - if status_id.present? - self&.pull_requests.select(:id, :status).where(status: status_id)&.pluck(:id).size - else - self&.pull_requests.select(:id)&.pluck(:id).size - end - end - - #创建项目管理员 - def check_project_members - return if owner.is_a?(Organization) - unless members.present? && members.exists?(user_id: self.user_id) - member_params = { - user_id: self.user_id, - project_id: self.id - } - user_member = Member.new(member_params) - if user_member.save - role_id = Role.select(:id,:position).where(position: 3)&.first&.id - MemberRole.create!(member_id: user_member.id ,role_id: role_id) - end - end - end - - - def self.init_bluck_repository - Project.includes(:repository).find_each do |project| - puts project.id - next if project.owner.blank? - if project.repository.blank? - puts "########### start create repositoy #############" - Repository.create!(project_id: project.id, identifier: Project.generate_identifier, user_id: project&.owner&.id) - end - end - end - - def self.generate_identifier - str_arr = (("a".."z").to_a + ("A".."Z").to_a) - - str = str_arr.shuffle[0..8].join - while Repository.exists?(identifier: str) - str = str_arr.shuffle[0..8].join - end - str - end - - def self.list_user_projects(user_id) - projects = Project.is_private.select(:id,:user_id) - user_not_show_1 = projects.where("user_id != ?",user_id).pluck(:id).uniq - - user_show_2 = projects.joins(:members).where("members.user_id = ?", user_id).pluck(:id).uniq - Project.where.not(id: (user_not_show_1 - user_show_2).uniq) - end - - def members_count - members.select(:id).size - end - - - def can_visited? - is_public? || User.current.admin? || member?(User.current) - end - - def releases_size(current_user_id, type) - if current_user_id == self.user_id && type.to_s == "all" - self.repository.version_releases_count - else - self.repository.version_releases.releases_size - end - end - - def contributor_users - self.pull_requests.select(:user_id).pluck(:user_id).uniq.size - end - - def open_issues_count - issues_count - closed_issues_count - end - - def numerical_for_project_type - self.class.name.constantize.project_types["#{self.project_type}"] - end - - def watched_by? user - watchers.pluck(:user_id).include? user&.id - end - - def praised_by? user - praise_treads.pluck(:user_id).include? user&.id - end - - def get_premission user - return "Owner" if owner?(user) - return "Manager" if manager?(user) - return "Developer" if develper?(user) - return "Reporter" if reporter?(user) - - return "" - end - - def fork_project - Project.find_by(id: self.forked_from_project_id) - end - - def self.members_projects(member_user_id) - joins(:members).where(members: { user_id: member_user_id}) - end - - def self.find_with_namespace(namespace_path, identifier) - logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} " - - user = Owner.find_by_login namespace_path - user = Owner.new(login: namespace_path) if user.nil? - project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}") - return nil if project.blank? - - [project, user] - end - - def ci_reactivate? - open_devops_count > 0 - end - - def ci_reactivate!(ci_repo) - ci_repo.update_column(:repo_active, 1) - update_column(:open_devops, true) - increment!(:open_devops_count) - end - - def self.sync_educoder_shixun(url, private_token, page, per_page) - SyncEducoderShixunJob.perform_later(url, private_token, page, per_page) - end - - def self.update_common_projects_count! - ps = ProjectStatistic.first - ps.increment!(:common_projects_count) unless ps.blank? - end - - def self.update_mirror_projects_count! - ps = ProjectStatistic.first - ps.increment!(:mirror_projects_count) unless ps.blank? - end - - def set_updated_on(time) - return if time.blank? - update_column(:updated_on, time) - end - - def is_transfering - applied_transfer_project&.common? ? true : false - end -end + +# == Schema Information +# +# Table name: projects +# +# id :integer not null, primary key +# name :string(255) default(""), not null +# description :text(4294967295) +# homepage :string(255) default("") +# is_public :boolean default("1"), not null +# parent_id :integer +# created_on :datetime +# updated_on :datetime +# identifier :string(255) +# status :integer default("1"), not null +# lft :integer +# rgt :integer +# inherit_members :boolean default("0"), not null +# project_type :integer default("0") +# hidden_repo :boolean default("0"), not null +# attachmenttype :integer default("1") +# user_id :integer +# dts_test :integer default("0") +# enterprise_name :string(255) +# organization_id :integer +# project_new_type :integer +# gpid :integer +# forked_from_project_id :integer +# forked_count :integer default("0") +# publish_resource :integer default("0") +# visits :integer default("0") +# hot :integer default("0") +# invite_code :string(255) +# qrcode :string(255) +# qrcode_expiretime :integer default("0") +# script :text(65535) +# training_status :integer default("0") +# rep_identifier :string(255) +# project_category_id :integer +# project_language_id :integer +# license_id :integer +# ignore_id :integer +# praises_count :integer default("0") +# watchers_count :integer default("0") +# issues_count :integer default("0") +# pull_requests_count :integer default("0") +# language :string(255) +# versions_count :integer default("0") +# issue_tags_count :integer default("0") +# closed_issues_count :integer default("0") +# open_devops :boolean default("0") +# gitea_webhook_id :integer +# open_devops_count :integer default("0") +# recommend :boolean default("0") +# platform :integer default("0") +# default_branch :string(255) default("master") +# website :string(255) +# lesson_url :string(255) +# is_pinned :boolean default("0") +# recommend_index :integer default("0") +# +# Indexes +# +# index_projects_on_forked_from_project_id (forked_from_project_id) +# index_projects_on_identifier (identifier) +# index_projects_on_invite_code (invite_code) +# index_projects_on_is_public (is_public) +# index_projects_on_lft (lft) +# index_projects_on_license_id (license_id) +# index_projects_on_name (name) +# index_projects_on_platform (platform) +# index_projects_on_project_category_id (project_category_id) +# index_projects_on_project_language_id (project_language_id) +# index_projects_on_project_type (project_type) +# index_projects_on_recommend (recommend) +# index_projects_on_rgt (rgt) +# index_projects_on_status (status) +# index_projects_on_updated_on (updated_on) +# + + + + +class Project < ApplicationRecord + include Matchable + include Publicable + include Watchable + include ProjectOperable + include Dcodes + + # common:开源托管项目 + # mirror:普通镜像项目,没有定时同步功能 + # sync_mirror:同步镜像项目,有系统定时同步功能,且用户可手动同步操作 + # + enum project_type: { sync_mirror: 2, mirror: 1, common: 0 } + + # forge: trustie平台项目, educoder: educoder平台项目, 默认为forge平台 + enum platform: { forge: 0, educoder: 1 } + + belongs_to :ignore, optional: true + belongs_to :license, optional: true + belongs_to :owner, class_name: 'Owner', foreign_key: :user_id, optional: true + belongs_to :organization_extension, foreign_key: :user_id, primary_key: :organization_id, optional: true, counter_cache: :num_projects + belongs_to :project_category, optional: true , :counter_cache => true + belongs_to :project_language, optional: true , :counter_cache => true + belongs_to :forked_from_project, class_name: 'Project', optional: true, foreign_key: :forked_from_project_id + has_many :project_trends, dependent: :destroy + has_many :watchers, as: :watchable, dependent: :destroy + has_many :fork_users, dependent: :destroy + has_many :forked_users, class_name: 'ForkUser', foreign_key: :fork_project_id, dependent: :destroy + has_many :forked_projects, class_name: 'Project', foreign_key: :forked_from_project_id + has_one :project_educoder, dependent: :destroy + + has_one :project_score, dependent: :destroy + has_one :repository, dependent: :destroy + has_many :pull_requests, dependent: :destroy + has_many :issue_tags, -> { order("issue_tags.created_at DESC") }, dependent: :destroy + has_many :issues, dependent: :destroy + # has_many :user_grades, dependent: :destroy + has_many :attachments, as: :container, dependent: :destroy + has_one :project_score, dependent: :destroy + has_many :versions, -> { order("versions.created_on DESC, versions.name DESC") }, dependent: :destroy + has_many :praise_treads, as: :praise_tread_object, dependent: :destroy + has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" + has_one :project_detail, dependent: :destroy + has_many :project_units, dependent: :destroy + has_one :applied_transfer_project,-> { order created_at: :desc }, dependent: :destroy + has_many :pinned_projects, dependent: :destroy + has_many :has_pinned_users, through: :pinned_projects, source: :user + has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id + after_create :incre_user_statistic, :incre_platform_statistic + after_save :check_project_members + before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data + before_destroy :decre_project_common, :decre_forked_from_project_count + after_destroy :decre_user_statistic, :decre_platform_statistic + scope :project_statics_select, -> {select(:id,:name, :is_public, :identifier, :status, :project_type, :user_id, :forked_count, :description, :visits, :project_category_id, :project_language_id, :license_id, :ignore_id, :watchers_count, :created_on)} + scope :no_anomory_projects, -> {where("projects.user_id is not null and projects.user_id != ?", 2)} + scope :recommend, -> { visible.project_statics_select.where(recommend: true) } + scope :pinned, -> {where(is_pinned: true)} + + delegate :content, to: :project_detail, allow_nil: true + delegate :name, to: :license, prefix: true, allow_nil: true + + validate :validate_sensitive_string + + def self.all_visible(user_id=nil) + user_projects_sql = Project.joins(:owner).where(users: {type: 'User'}).to_sql + org_public_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'common'})).to_sql + if user_id.present? + org_limit_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension).where(organization_extensions: {visibility: 'limited'})).to_sql + org_privacy_projects_sql = Project.joins(:owner).merge(Organization.joins(:organization_extension, :organization_users).where(organization_extensions: {visibility: 'privacy'}, organization_users: {user_id: user_id})).to_sql + return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } UNION #{ org_limit_projects_sql } UNION #{org_privacy_projects_sql} ) AS projects").visible + else + return Project.from("( #{ user_projects_sql } UNION #{ org_public_projects_sql } ) AS projects").visible + end + end + + def reset_cache_data + CacheAsyncResetJob.set(wait: 5.seconds).perform_later("project_common_service", self.id) + if changes[:user_id].present? + CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1}, changes[:user_id].first) + CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1}, changes[:user_id].last) + end + if changes[:project_language_id].present? + first_language = ProjectLanguage.find_by_id(changes[:project_language_id].first) + last_language = ProjectLanguage.find_by_id(changes[:project_language_id].last) + CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}, self.user_id) + CacheAsyncSetJob.perform_later("user_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}, self.user_id) + CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: first_language&.name, project_language_count: -1}) + CacheAsyncSetJob.perform_later("platform_statistic_service", {project_language_count_key: last_language&.name, project_language_count: 1}) + end + if changes[:is_public].present? + if changes[:is_public][0] && !changes[:is_public][1] + CacheAsyncClearJob.perform_later('project_rank_service', self.id) + end + if !changes[:is_public][0] && changes[:is_public][1] + $redis_cache.srem("v2-project-rank-deleted", self.id) + end + end + end + + def decre_project_common + CacheAsyncClearJob.perform_later('project_common_service', self.id) + end + + def decre_forked_from_project_count + forked_project = self.forked_from_project + if forked_project.present? + forked_project.decrement(:forked_count, 1) + forked_project.save + end + end + + def incre_user_statistic + CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}, self.user_id) + end + + def decre_user_statistic + CacheAsyncSetJob.perform_later("user_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}, self.user_id) + end + + def incre_platform_statistic + CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: 1, project_language_count_key: self.project_language&.name, project_language_count: 1}) + end + + def decre_platform_statistic + CacheAsyncSetJob.perform_later("platform_statistic_service", {project_count: -1, project_language_count_key: self.project_language&.name, project_language_count: -1}) + end + + def is_full_public + owner = self.owner + if owner.is_a?(Organization) + return self.is_public && owner&.visibility == "common" + else + return self.is_public + end + end + + def reset_unmember_followed + if changes[:is_public].present? && changes[:is_public] == [true, false] + self.watchers.where.not(user_id: self.all_collaborators).destroy_all + end + end + + def set_invite_code + if self.invite_code.nil? + self.invite_code= self.generate_dcode('invite_code', 6) + end + end + + def set_recommend_and_is_pinned + self.recommend = self.recommend_index.zero? ? false : true + # 私有项目不允许设置精选和推荐 + unless self.is_public + self.recommend = false + self.recommend_index = 0 + self.is_pinned = false + end + end + + def self.search_project(search) + ransack(name_or_identifier_cont: search) + end + # 创建者 + def creator + User.find(user_id).full_name + end + + def members_user_infos + members.joins(:roles).where("roles.name in ('Manager', 'Developer', 'Reporter')").joins("left join users on members.user_id = users.id ").includes(:user).where("users.type = ?", "User") + # members.joins("left join users on members.user_id = users.id").select("users.id", "users.login","users.firstname","users.lastname") + # .pluck("users.id", "users.login","users.lastname", "users.firstname") + end + + def to_param + self.identifier.parameterize + end + + def get_issues_count(status_id) + if status_id.present? + self&.issues.issue_issue.select(:id, :status_id).where(status_id: status_id)&.pluck(:id).size + else + self&.issues.issue_issue.select(:id)&.pluck(:id).size + end + end + + def get_pull_requests_count(status_id) + if status_id.present? + self&.pull_requests.select(:id, :status).where(status: status_id)&.pluck(:id).size + else + self&.pull_requests.select(:id)&.pluck(:id).size + end + end + + #创建项目管理员 + def check_project_members + return if owner.is_a?(Organization) + unless members.present? && members.exists?(user_id: self.user_id) + member_params = { + user_id: self.user_id, + project_id: self.id + } + user_member = Member.new(member_params) + if user_member.save + role_id = Role.select(:id,:position).where(position: 3)&.first&.id + MemberRole.create!(member_id: user_member.id ,role_id: role_id) + end + end + end + + + def self.init_bluck_repository + Project.includes(:repository).find_each do |project| + puts project.id + next if project.owner.blank? + if project.repository.blank? + puts "########### start create repositoy #############" + Repository.create!(project_id: project.id, identifier: Project.generate_identifier, user_id: project&.owner&.id) + end + end + end + + def self.generate_identifier + str_arr = (("a".."z").to_a + ("A".."Z").to_a) + + str = str_arr.shuffle[0..8].join + while Repository.exists?(identifier: str) + str = str_arr.shuffle[0..8].join + end + str + end + + def self.list_user_projects(user_id) + projects = Project.is_private.select(:id,:user_id) + user_not_show_1 = projects.where("user_id != ?",user_id).pluck(:id).uniq + + user_show_2 = projects.joins(:members).where("members.user_id = ?", user_id).pluck(:id).uniq + Project.where.not(id: (user_not_show_1 - user_show_2).uniq) + end + + def members_count + members.select(:id).size + end + + + def can_visited? + is_public? || User.current.admin? || member?(User.current) + end + + def releases_size(current_user_id, type) + if current_user_id == self.user_id && type.to_s == "all" + self.repository.version_releases_count + else + self.repository.version_releases.releases_size + end + end + + def contributor_users + self.pull_requests.select(:user_id).pluck(:user_id).uniq.size + end + + def open_issues_count + issues_count - closed_issues_count + end + + def numerical_for_project_type + self.class.name.constantize.project_types["#{self.project_type}"] + end + + def watched_by? user + watchers.pluck(:user_id).include? user&.id + end + + def praised_by? user + praise_treads.pluck(:user_id).include? user&.id + end + + def get_premission user + return "Owner" if owner?(user) + return "Manager" if manager?(user) + return "Developer" if develper?(user) + return "Reporter" if reporter?(user) + + return "" + end + + def fork_project + Project.find_by(id: self.forked_from_project_id) + end + + def self.members_projects(member_user_id) + joins(:members).where(members: { user_id: member_user_id}) + end + + def self.find_with_namespace(namespace_path, identifier) + logger.info "########namespace_path: #{namespace_path} ########identifier: #{identifier} " + + user = Owner.find_by_login namespace_path + user = Owner.new(login: namespace_path) if user.nil? + project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}") + return nil if project.blank? + + [project, user] + end + + def ci_reactivate? + open_devops_count > 0 + end + + def ci_reactivate!(ci_repo) + ci_repo.update_column(:repo_active, 1) + update_column(:open_devops, true) + increment!(:open_devops_count) + end + + def self.sync_educoder_shixun(url, private_token, page, per_page) + SyncEducoderShixunJob.perform_later(url, private_token, page, per_page) + end + + def self.update_common_projects_count! + ps = ProjectStatistic.first + ps.increment!(:common_projects_count) unless ps.blank? + end + + def self.update_mirror_projects_count! + ps = ProjectStatistic.first + ps.increment!(:mirror_projects_count) unless ps.blank? + end + + def set_updated_on(time) + return if time.blank? + update_column(:updated_on, time) + end + + def is_transfering + applied_transfer_project&.common? ? true : false + end + + def validate_sensitive_string + raise("项目名称包含敏感词汇,请重新输入") if name && !HarmoniousDictionary.clean?(name) + raise("项目描述包含敏感词汇,请重新输入") if description && !HarmoniousDictionary.clean?(description) + end +end From 877948b0cd515eb414891801e63f8cd934e77368 Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 1 Jun 2022 10:00:57 +0800 Subject: [PATCH 009/242] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E6=B6=88=E6=81=AF=E6=A8=A1=E6=9D=BF=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admins/message_templates_controller.rb | 20 +++++++++++++++++-- app/models/message_template.rb | 3 +-- .../admins/message_templates/_list.html.erb | 4 ++-- .../admins/message_templates/index.html.erb | 8 +++++++- app/views/admins/message_templates/new.js.erb | 2 ++ config/routes.rb | 2 +- 6 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 app/views/admins/message_templates/new.js.erb diff --git a/app/controllers/admins/message_templates_controller.rb b/app/controllers/admins/message_templates_controller.rb index 23c94e78..af77858e 100644 --- a/app/controllers/admins/message_templates_controller.rb +++ b/app/controllers/admins/message_templates_controller.rb @@ -2,8 +2,24 @@ class Admins::MessageTemplatesController < Admins::BaseController before_action :get_template, only: [:edit, :update, :destroy] def index - message_templates = MessageTemplate.group(:type).count.keys - @message_templates = kaminari_array_paginate(message_templates) + message_templates = MessageTemplate.ransack(sys_notice_or_email_or_email_title_cont: params[:search]).result + @message_templates = kaminari_paginate(message_templates) + end + + def new + @message_template = MessageTemplate::CustomTip.new + end + + def create + @message_template = MessageTemplate::CustomTip.new(ignore_params) + + if @message_template.save! + redirect_to admins_message_templates_path + flash[:success] = "创建消息模板成功" + else + render :new + flash[:danger] = "创建消息模板失败" + end end def edit diff --git a/app/models/message_template.rb b/app/models/message_template.rb index f3420013..50de7d8e 100644 --- a/app/models/message_template.rb +++ b/app/models/message_template.rb @@ -16,8 +16,7 @@ class MessageTemplate < ApplicationRecord PLATFORM = 'GitLink' def self.build_init_data - MessageTemplate::IssueAssignerExpire.destroy_all - MessageTemplate::IssueCreatorExpire.destroy_all + MessageTemplate.where.not(type: 'MessageTemplate::CustomTip').destroy_all self.create(type: 'MessageTemplate::FollowedTip', sys_notice: '{nickname} 关注了你', notification_url: '{baseurl}/{login}') email_html = File.read("#{email_template_html_dir}/issue_assigned.html") self.create(type: 'MessageTemplate::IssueAssigned', sys_notice: '{nickname1}在 {nickname2}/{repository} 指派给你一个疑修:{title}', notification_url: '{baseurl}/{owner}/{identifier}/issues/{id}', email: email_html, email_title: "#{PLATFORM}: {nickname1} 在 {nickname2}/{repository} 指派给你一个疑修") diff --git a/app/views/admins/message_templates/_list.html.erb b/app/views/admins/message_templates/_list.html.erb index 4082735f..dae87d57 100644 --- a/app/views/admins/message_templates/_list.html.erb +++ b/app/views/admins/message_templates/_list.html.erb @@ -11,8 +11,8 @@ <% if message_templates.present? %> - <% message_templates.each_with_index do |message_template_type, index| %> - <% message_template = message_template_type.constantize.last%> + <% message_templates.each_with_index do |message_template, index| %> + <%# message_template = message_template_type.constantize.last%> <%= list_index_no((params[:page] || 1).to_i, index) %> <%= message_template.simple_type %> diff --git a/app/views/admins/message_templates/index.html.erb b/app/views/admins/message_templates/index.html.erb index 54d27333..e14de841 100644 --- a/app/views/admins/message_templates/index.html.erb +++ b/app/views/admins/message_templates/index.html.erb @@ -3,7 +3,13 @@ <% end %>
    - <%= link_to "初始化数据", init_data_admins_message_templates_path, class: "btn btn-primary pull-right", "data-disabled-with":"...初始化数据" %> + <%= form_tag(admins_message_templates_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %> + <%= text_field_tag(:search, params[:search], class: 'form-control col-12 col-md-2 mr-3', placeholder: '名称检索') %> + <%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %> + + <% end %> + <%= link_to "初始化数据", init_data_admins_message_templates_path, class: "btn btn-primary mr-3 pull-right", "data-disabled-with":"...初始化数据" %> + <%= link_to "新增", new_admins_message_template_path, remote: true, class: "btn btn-primary pull-right", "data-disabled-with":"...新增" %>
    <%= render partial: 'admins/message_templates/list', locals: { message_templates: @message_templates } %> diff --git a/app/views/admins/message_templates/new.js.erb b/app/views/admins/message_templates/new.js.erb new file mode 100644 index 00000000..e75d923d --- /dev/null +++ b/app/views/admins/message_templates/new.js.erb @@ -0,0 +1,2 @@ +$("#admins-message-templates-content").html("<%= j render partial: 'admins/message_templates/form', locals:{type: 'create'} %>") +createMDEditor('message-template-email-editor', { height: 500, placeholder: '请输入邮件模版' }); diff --git a/config/routes.rb b/config/routes.rb index 4d1e3ade..b585f077 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -694,7 +694,7 @@ Rails.application.routes.draw do get :history end end - resources :message_templates, only: [:index, :edit, :update] do + resources :message_templates, only: [:index, :new, :create, :edit, :update] do collection do get :init_data end From a07204111a83813d0dc73c48cd63168ebc63991b Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Wed, 1 Jun 2022 15:24:39 +0800 Subject: [PATCH 010/242] =?UTF-8?q?=E9=80=9A=E7=9F=A5=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=94=A8=E6=88=B7=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E6=97=B6=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/send_template_message_job.rb | 9 ++++++++- app/models/message_template/custom_tip.rb | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/jobs/send_template_message_job.rb b/app/jobs/send_template_message_job.rb index bec3f7b0..557d4d0f 100644 --- a/app/jobs/send_template_message_job.rb +++ b/app/jobs/send_template_message_job.rb @@ -8,13 +8,20 @@ class SendTemplateMessageJob < ApplicationJob receivers_id, template_id, props = args[0], args[1], args[2] template = MessageTemplate.find_by_id(template_id) return unless template.present? - receivers = User.where(id: receivers_id) + receivers = User.where(id: receivers_id).or(User.where(mail: receivers_id)) + not_exists_receivers = receivers_id - receivers.pluck(:id) - receivers.pluck(:mail) receivers_string, content, notification_url = MessageTemplate::CustomTip.get_message_content(receivers, template, props) Notice::Write::CreateService.call(receivers_string, content, notification_url, source, {receivers_id: receivers_id, template_id: template_id, props: props}) receivers.find_each do |receiver| receivers_email_string, email_title, email_content = MessageTemplate::CustomTip.get_email_message_content(receiver, template, props) Notice::Write::EmailCreateService.call(receivers_email_string, email_title, email_content) end + not_exists_receivers.each do |mail| + valid_email_regex = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i + next unless (mail =~ valid_email_regex) + email_title, email_content = MessageTemplate::CustomTip.get_email_content(template, props) + Notice::Write::EmailCreateService.call(mail, email_title, email_content) + end when 'FollowTip' watcher_id = args[0] watcher = Watcher.find_by_id(watcher_id) diff --git a/app/models/message_template/custom_tip.rb b/app/models/message_template/custom_tip.rb index 6c5a3345..f1e3096f 100644 --- a/app/models/message_template/custom_tip.rb +++ b/app/models/message_template/custom_tip.rb @@ -48,4 +48,21 @@ class MessageTemplate::CustomTip < MessageTemplate Rails.logger.info("MessageTemplate::CustomTip.get_email_message_content [ERROR] #{e}") return '', '', '' end + + def self.get_email_content(template, props = {}) + return '', '', '' if template.blank? + title = template.email_title + content = template.email + props.each do |k, v| + title.gsub!("{#{k}}", v) + content.gsub!("{#{k}}", v) + end + title.gsub!('{platform}', PLATFORM) + content.gsub!('{platform}', PLATFORM) + + return title, content + rescue => e + Rails.logger.info("MessageTemplate::CustomTip.get_email_content [ERROR] #{e}") + return '', '' + end end \ No newline at end of file From 0b543f7ddab712fe6f0f77a64da5fe7aa627f439 Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Wed, 1 Jun 2022 16:08:20 +0800 Subject: [PATCH 011/242] =?UTF-8?q?=E9=80=9A=E7=9F=A5=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=94=A8=E6=88=B7=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E6=97=B6=E5=8F=91=E9=80=81base=5Furl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/message_template/custom_tip.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/message_template/custom_tip.rb b/app/models/message_template/custom_tip.rb index f1e3096f..b0f628d6 100644 --- a/app/models/message_template/custom_tip.rb +++ b/app/models/message_template/custom_tip.rb @@ -25,6 +25,7 @@ class MessageTemplate::CustomTip < MessageTemplate content.gsub!("{#{k}}", v) notification_url.gsub!("{#{k}}", v) end + notification_url.gsub!('{baseurl}', base_url) return receivers_string(receivers), content, notification_url rescue => e Rails.logger.info("MessageTemplate::CustomTip.get_message_content [ERROR] #{e}") @@ -42,6 +43,7 @@ class MessageTemplate::CustomTip < MessageTemplate content.gsub!('{receiver}', receiver&.real_name) title.gsub!('{platform}', PLATFORM) content.gsub!('{platform}', PLATFORM) + content.gsub!('{baseurl}', base_url) return receiver&.mail, title, content rescue => e @@ -59,6 +61,7 @@ class MessageTemplate::CustomTip < MessageTemplate end title.gsub!('{platform}', PLATFORM) content.gsub!('{platform}', PLATFORM) + content.gsub!('{baseurl}', base_url) return title, content rescue => e From 8ae55875165708bec23d081f74a927f99f559626 Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 8 Jun 2022 10:37:31 +0800 Subject: [PATCH 012/242] fix: search name use strip --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index fa940c29..bc9808c6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -179,7 +179,7 @@ class User < Owner scope :active, lambda { where(status: STATUS_ACTIVE) } scope :like, lambda { |keywords| sql = "CONCAT(lastname, firstname) LIKE :search OR nickname LIKE :search OR login LIKE :search OR mail LIKE :search OR nickname LIKE :search" - where(sql, :search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? + where(sql, :search => "%#{keywords.strip}%") unless keywords.blank? } scope :simple_select, -> {select(:id, :login, :lastname,:firstname, :nickname, :gitea_uid, :type)} From 3e4dd90e1737ebc908a650f3bb44ec3e59d6d6fd Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 8 Jun 2022 10:46:55 +0800 Subject: [PATCH 013/242] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=90=8D=E7=A7=B0=E5=B1=95=E7=A4=BA=E6=97=B6=E5=8F=AA?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E9=A6=96=E6=9C=AB=E7=9A=84=E7=A9=BA=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index bc9808c6..d3803a9f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -553,7 +553,8 @@ class User < Owner return '游客' unless logged? name = lastname + firstname name = name.blank? ? (nickname.blank? ? login : nickname) : name - name.gsub(/\s+/, '').strip #6.11 -hs + # name.gsub(/\s+/, '').strip #6.11 -hs + name.strip end def only_real_name From ce467736a71d087e898440faa83841342f9ead87 Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 13 Jun 2022 12:53:40 +0800 Subject: [PATCH 014/242] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20readme=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=AD=A3=E5=B8=B8=E8=B7=B3=E8=BD=AC=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/repositories_helper.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 73ce9823..c31b9c59 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -104,8 +104,7 @@ module RepositoriesHelper # 链接直接跳过不做替换 next if s_content.starts_with?('http://') || s_content.starts_with?('https://') || s_content.starts_with?('mailto:') || s_content.blank? ext = File.extname(s_content)[1..-1] - - if image_type?(ext) || download_type(ext) + if (image_type?(ext) || download_type(ext)) && !s_content.to_s.end_with?('/') s_content = File.expand_path(s_content, file_path) s_content = s_content.split("#{Rails.root}/")[1] # content = content.gsub(s[0], "/#{s_content}") From 66c40d479163f528f65a7b14e72230c98c4d262b Mon Sep 17 00:00:00 2001 From: xiaoxiaoqiong Date: Wed, 15 Jun 2022 11:16:08 +0800 Subject: [PATCH 015/242] =?UTF-8?q?fixed=20gitea=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/interactors/gitea/register_interactor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/interactors/gitea/register_interactor.rb b/app/interactors/gitea/register_interactor.rb index d8dbc6ca..e188809a 100644 --- a/app/interactors/gitea/register_interactor.rb +++ b/app/interactors/gitea/register_interactor.rb @@ -13,7 +13,7 @@ module Gitea end def success? - @error.nil? + @error.nil? && @result[:status].to_s == "success" end def result From 42d2b51f7f2df6788119d2c3489dcdd8c5ab4947 Mon Sep 17 00:00:00 2001 From: yystopf Date: Thu, 16 Jun 2022 13:42:17 +0800 Subject: [PATCH 016/242] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=95=8F=E6=84=9F=E8=AF=8D=E5=88=A4=E5=AE=9A=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E5=88=9B=E5=BB=BA=E6=88=96=E8=80=85=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/project.rb | 2 +- app/models/project_unit.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 9c77685f..edaa00eb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -141,7 +141,7 @@ class Project < ApplicationRecord delegate :content, to: :project_detail, allow_nil: true delegate :name, to: :license, prefix: true, allow_nil: true - validate :validate_sensitive_string + validate :validate_sensitive_string, on: [:create, :update] def self.all_visible(user_id=nil) user_projects_sql = Project.joins(:owner).where(users: {type: 'User'}).to_sql diff --git a/app/models/project_unit.rb b/app/models/project_unit.rb index cc35a6b2..01f6c667 100644 --- a/app/models/project_unit.rb +++ b/app/models/project_unit.rb @@ -14,6 +14,8 @@ # class ProjectUnit < ApplicationRecord + + default_scope {where(unit_type: ProjectUnit.unit_types.keys)} belongs_to :project enum unit_type: {code: 1, issues: 2, pulls: 3, wiki:4, devops: 5, versions: 6, resources: 7} From c42f3d59281ba65edc85873e6ff3a9d551968410 Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 15 Jun 2022 18:07:23 +0800 Subject: [PATCH 017/242] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E7=94=A8=E6=88=B7=E9=93=BE=E6=8E=A5=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project_invite_links_controller.rb | 36 +++++++++ .../project_invite_links/create_form.rb | 8 ++ app/models/applied_project.rb | 21 ++--- app/models/project.rb | 1 + app/models/project_invite_link.rb | 57 ++++++++++++++ app/services/projects/link_join_service.rb | 76 +++++++++++++++++++ app/views/projects/_detail.json.jbuilder | 8 ++ .../_detail.json.jbuilder | 12 +++ .../current_link.json.jbuilder | 1 + config/routes.rb | 7 ++ ...20614070028_create_project_invite_links.rb | 16 ++++ ...project_invite_link_to_applied_projects.rb | 6 ++ spec/models/project_invite_link_spec.rb | 5 ++ 13 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 app/controllers/projects/project_invite_links_controller.rb create mode 100644 app/forms/projects/project_invite_links/create_form.rb create mode 100644 app/models/project_invite_link.rb create mode 100644 app/services/projects/link_join_service.rb create mode 100644 app/views/projects/_detail.json.jbuilder create mode 100644 app/views/projects/project_invite_links/_detail.json.jbuilder create mode 100644 app/views/projects/project_invite_links/current_link.json.jbuilder create mode 100644 db/migrate/20220614070028_create_project_invite_links.rb create mode 100644 db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb create mode 100644 spec/models/project_invite_link_spec.rb diff --git a/app/controllers/projects/project_invite_links_controller.rb b/app/controllers/projects/project_invite_links_controller.rb new file mode 100644 index 00000000..59f618fc --- /dev/null +++ b/app/controllers/projects/project_invite_links_controller.rb @@ -0,0 +1,36 @@ +class Projects::ProjectInviteLinksController < Projects::BaseController + before_action :require_manager!, except: [:redirect_link] + before_action :require_login + + def current_link + @project_invite_link = ProjectInviteLink.find_by(user_id: current_user.id, project_id: @project.id) + @project_invite_link = ProjectInviteLink.build!(@project, current_user) unless @project_invite_link.present? + end + + def generate_link + ActiveRecord::Base.transaction do + params_data = link_params.merge({user_id: current_user.id, project_id: @project.id}) + puts params_data + Projects::ProjectInviteLinks::CreateForm.new(params_data).validate! + @project_invite_link = ProjectInviteLink.create!(params_data.merge(sign: ProjectInviteLink.random_hex_sign)) + render_ok + end + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + def redirect_link + Projects::LinkJoinService.call(current_user, @project, params[:invite_sign]) + render_ok + rescue Exception => e + uid_logger_error(e.message) + tip_exception(e.message) + end + + + private + def link_params + params.require(:project_invite_link).permit(:role, :is_apply) + end +end diff --git a/app/forms/projects/project_invite_links/create_form.rb b/app/forms/projects/project_invite_links/create_form.rb new file mode 100644 index 00000000..701625c0 --- /dev/null +++ b/app/forms/projects/project_invite_links/create_form.rb @@ -0,0 +1,8 @@ +class Projects::ProjectInviteLinks::CreateForm < BaseForm + attr_accessor :user_id, :project_id, :role, :is_apply + + validates :user_id, :project_id, :role, presence: true + validates :role, inclusion: { in: %w(manager developer reporter), message: "请输入正确的权限." } + validates :is_apply, inclusion: {in: [true, false], message: "请输入是否需要管理员审核."} +end + \ No newline at end of file diff --git a/app/models/applied_project.rb b/app/models/applied_project.rb index ea7ca6ee..2b18d4d8 100644 --- a/app/models/applied_project.rb +++ b/app/models/applied_project.rb @@ -2,24 +2,27 @@ # # Table name: forge_applied_projects # -# id :integer not null, primary key -# project_id :integer -# user_id :integer -# role :integer default("0") -# status :integer default("0") -# created_at :datetime not null -# updated_at :datetime not null +# id :integer not null, primary key +# project_id :integer +# user_id :integer +# role :integer default("0") +# status :integer default("0") +# created_at :datetime not null +# updated_at :datetime not null +# project_invite_link_id :integer # # Indexes # -# index_forge_applied_projects_on_project_id (project_id) -# index_forge_applied_projects_on_user_id (user_id) +# index_forge_applied_projects_on_project_id (project_id) +# index_forge_applied_projects_on_project_invite_link_id (project_invite_link_id) +# index_forge_applied_projects_on_user_id (user_id) # class AppliedProject < ApplicationRecord self.table_name = "forge_applied_projects" belongs_to :user belongs_to :project + belongs_to :project_invite_link, optional: true has_many :applied_messages, as: :applied, dependent: :destroy # has_many :forge_activities, as: :forge_act, dependent: :destroy diff --git a/app/models/project.rb b/app/models/project.rb index edaa00eb..92e1bac7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -128,6 +128,7 @@ class Project < ApplicationRecord has_many :pinned_projects, dependent: :destroy has_many :has_pinned_users, through: :pinned_projects, source: :user has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id + has_many :project_invite_links, dependent: :destroy after_create :incre_user_statistic, :incre_platform_statistic after_save :check_project_members before_save :set_invite_code, :reset_unmember_followed, :set_recommend_and_is_pinned, :reset_cache_data diff --git a/app/models/project_invite_link.rb b/app/models/project_invite_link.rb new file mode 100644 index 00000000..fa5c0c44 --- /dev/null +++ b/app/models/project_invite_link.rb @@ -0,0 +1,57 @@ +# == Schema Information +# +# Table name: project_invite_links +# +# id :integer not null, primary key +# project_id :integer +# user_id :integer +# role :integer default("4") +# is_apply :boolean default("1") +# sign :string(255) +# expired_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_project_invite_links_on_project_id (project_id) +# index_project_invite_links_on_sign (sign) +# index_project_invite_links_on_user_id (user_id) +# + +class ProjectInviteLink < ApplicationRecord + + default_scope { where("expired_at > ?", Time.now).or(where(expired_at: nil)) } + + belongs_to :project + belongs_to :user + has_many :applied_projects + + scope :with_project_id, -> (project_id) {where(project_id: project_id)} + scope :with_user_id, -> (user_id) {where(user_id: user_id)} + + enum role: {manager: 3, developer: 4, reporter: 5} + + before_create :set_old_data_expired_at + + def self.random_hex_sign + SecureRandom.hex + end + + def self.build!(project, user, role="developer", is_apply=true) + self.create!( + project_id: project&.id, + user_id: user&.id, + role: role, + is_apply: is_apply, + sign: random_hex_sign + ) + end + + private + def set_old_data_expired_at + ProjectInviteLink.where(user_id: self.user_id, project_id: self.project).update_all(expired_at: Time.now) + end + + +end diff --git a/app/services/projects/link_join_service.rb b/app/services/projects/link_join_service.rb new file mode 100644 index 00000000..c42376ef --- /dev/null +++ b/app/services/projects/link_join_service.rb @@ -0,0 +1,76 @@ +class Projects::LinkJoinService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :user, :project, :invite_sign, :params + + def initialize(user, project, invite_sign, params={}) + @user = user + @project = project + @invite_sign = invite_sign + @params = params + end + + def call + ActiveRecord::Base.transaction do + validate! + if invite_link.is_apply + # 如果需要申请才能加入,创建一条申请记录 + create_applied_project! + else + # 如果不需要申请,直接为项目添加该成员 + create_member! + end + end + end + + private + def validate! + raise Error, 'invite_sign必须存在!' if invite_sign.blank? + raise Error, '邀请链接不存在!' unless invite_link.present? + raise Error, '邀请链接已失效!' unless invite_user_in_project + raise Error, '用户已加入该项目!' if project.member?(user.id) + end + + def create_applied_project! + user.applied_projects.create!(project: project, role: role_value) + end + + def create_member! + Projects::AddMemberInteractor.call(project.owner, project, user, permission) + end + + def invite_link + ProjectInviteLink.find_by(project_id: project.id, sign: invite_sign) + end + + def invite_user_in_project + in_project = project.member?(invite_link.user) + invite_link.update_column(:expired_at, Time.now) unless in_project + in_project + end + + def role_value + @_role ||= + case invite_link&.role + when 'manager' then 3 + when 'developer' then 4 + when 'reporter' then 5 + else + 5 + end + end + + def permission + case invite_link&.role + when 'manager' + 'admin' + when 'developer' + 'write' + when 'reporter' + 'read' + else + 'read' + end + end + +end \ No newline at end of file diff --git a/app/views/projects/_detail.json.jbuilder b/app/views/projects/_detail.json.jbuilder new file mode 100644 index 00000000..ed23fdef --- /dev/null +++ b/app/views/projects/_detail.json.jbuilder @@ -0,0 +1,8 @@ +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 \ No newline at end of file diff --git a/app/views/projects/project_invite_links/_detail.json.jbuilder b/app/views/projects/project_invite_links/_detail.json.jbuilder new file mode 100644 index 00000000..7c9f78dd --- /dev/null +++ b/app/views/projects/project_invite_links/_detail.json.jbuilder @@ -0,0 +1,12 @@ +json.(project_invite_link, :id, :role, :is_apply, :sign) + +json.user do + json.partial! "/users/user_simple", locals: {user: project_invite_link.user} +end +if project_invite_link&.project.present? + json.project do + json.partial! "/projects/detail", locals: {project: project_invite_link.project} + end +else + json.project nil +end diff --git a/app/views/projects/project_invite_links/current_link.json.jbuilder b/app/views/projects/project_invite_links/current_link.json.jbuilder new file mode 100644 index 00000000..1903e10a --- /dev/null +++ b/app/views/projects/project_invite_links/current_link.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'detail', locals: { project_invite_link: @project_invite_link } diff --git a/config/routes.rb b/config/routes.rb index b585f077..ed28b5ba 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -603,6 +603,13 @@ Rails.application.routes.draw do post :cancel end end + resources :project_invite_links, only: [:index] do + collection do + get :current_link + post :generate_link + post :redirect_link + end + end resources :webhooks, except: [:show, :new] do member do get :tasks diff --git a/db/migrate/20220614070028_create_project_invite_links.rb b/db/migrate/20220614070028_create_project_invite_links.rb new file mode 100644 index 00000000..5b482967 --- /dev/null +++ b/db/migrate/20220614070028_create_project_invite_links.rb @@ -0,0 +1,16 @@ +class CreateProjectInviteLinks < ActiveRecord::Migration[5.2] + def change + create_table :project_invite_links do |t| + t.references :project + t.references :user + t.integer :role, default: 4 + t.boolean :is_apply, default: true + t.string :sign + t.datetime :expired_at + + t.timestamps + end + + add_index :project_invite_links, :sign + end +end diff --git a/db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb b/db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb new file mode 100644 index 00000000..31cbd951 --- /dev/null +++ b/db/migrate/20220614081950_add_project_invite_link_to_applied_projects.rb @@ -0,0 +1,6 @@ +class AddProjectInviteLinkToAppliedProjects < ActiveRecord::Migration[5.2] + def change + add_column :forge_applied_projects, :project_invite_link_id, :integer + add_index :forge_applied_projects, :project_invite_link_id + end +end diff --git a/spec/models/project_invite_link_spec.rb b/spec/models/project_invite_link_spec.rb new file mode 100644 index 00000000..d7c46fa5 --- /dev/null +++ b/spec/models/project_invite_link_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ProjectInviteLink, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From f6fb50529c9a8db640ade92c66d3ca6b5542e281 Mon Sep 17 00:00:00 2001 From: yystopf Date: Thu, 16 Jun 2022 11:56:09 +0800 Subject: [PATCH 018/242] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E9=A1=B9=E7=9B=AE=E9=93=BE=E6=8E=A5=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project_invite_links_controller.rb | 1 - app/docs/slate/source/includes/_projects.md | 186 ++++++++ app/services/projects/link_join_service.rb | 2 +- .../generate_link.json.jbuilder | 1 + public/docs/api.html | 450 ++++++++++++++++-- 5 files changed, 597 insertions(+), 43 deletions(-) create mode 100644 app/views/projects/project_invite_links/generate_link.json.jbuilder diff --git a/app/controllers/projects/project_invite_links_controller.rb b/app/controllers/projects/project_invite_links_controller.rb index 59f618fc..c177367c 100644 --- a/app/controllers/projects/project_invite_links_controller.rb +++ b/app/controllers/projects/project_invite_links_controller.rb @@ -13,7 +13,6 @@ class Projects::ProjectInviteLinksController < Projects::BaseController puts params_data Projects::ProjectInviteLinks::CreateForm.new(params_data).validate! @project_invite_link = ProjectInviteLink.create!(params_data.merge(sign: ProjectInviteLink.random_hex_sign)) - render_ok end rescue Exception => e uid_logger_error(e.message) diff --git a/app/docs/slate/source/includes/_projects.md b/app/docs/slate/source/includes/_projects.md index e133bcdd..7e700457 100644 --- a/app/docs/slate/source/includes/_projects.md +++ b/app/docs/slate/source/includes/_projects.md @@ -1,4 +1,190 @@ # Projects +## 获取项目邀请链接 +当前登录(管理员)用户获取项目邀请链接的接口(第一次请求会默认生成role类型为developer和is_apply为true的链接) + +> 示例: + +```shell +curl -X GET http://localhost:3000/api/yystopf/kellect/project_invite_links/current_link.json +``` + +```javascript +await octokit.request('GET /api/yystopf/kellect/project_invite_links/current_link.json') +``` + +### HTTP 请求 +`GET /api/:owner/:repo/project_invite_links/current_link.json` + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |链接id | +|role |string |邀请角色| +|is_apply |boolean |是否需要审核 | +|sign |string |邀请标识(放在链接后面即可)| +|user.id |int |链接创建者的id | +|user.type |string |链接创建者的类型 | +|user.name |string |链接创建者的名称 | +|user.login |string |链接创建者的标识 | +|user.image_url |string |链接创建者头像 | +|project.id |int |链接关联项目的id | +|project.identifier |string |链接关联项目的标识 | +|project.name |string |链接关联项目的名称 | +|project.description |string |链接关联项目的描述 | +|project.is_public |bool |链接关联项目是否公开 | +|project.owner.id |bool |链接关联项目拥有者id | +|project.owner.type |string |链接关联项目拥有者类型 | +|project.owner.name |string |链接关联项目拥有者昵称 | +|project.owner.login |string |链接关联项目拥有者标识 | +|project.owner.image_url|string |链接关联项目拥有者头像 | + +> 返回的JSON示例: + +```json +{ + "id": 7, + "role": "developer", + "is_apply": false, + "sign": "6b6b454843c291d4e52e60853cb8ad9f", + "user": { + "id": 2, + "type": "User", + "name": "heh", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png" + }, + "project": { + "id": 474, + "identifier": "kellect", + "name": "kellect", + "description": null, + "is_public": true, + "owner": { + "id": 2, + "type": "User", + "name": "heh", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png" + } + } +} +``` +## 生成项目邀请链接 +当前登录(管理员)用户生成的项目邀请链接,可选role和is_apply参数 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/yystopf/kellect/project_invite_links/generate_link.json +``` + +```javascript +await octokit.request('POST /api/yystopf/kellect/project_invite_links/generate_link.json') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/project_invite_links/generate_link.json` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|role |是| |string |项目权限,reporter: 报告者, developer: 开发者,manager:管理员 | +|is_apply |是| |boolean |是否需要审核 | + + +> 请求的JSON示例 + +```json +{ + "role": "developer", + "is_apply": false +} +``` + +### 返回字段说明 +参数 | 类型 | 字段说明 +--------- | ----------- | ----------- +|id |int |链接id | +|role |string |邀请角色| +|is_apply |boolean |是否需要审核 | +|sign |string |邀请标识(放在链接后面即可)| +|user.id |int |链接创建者的id | +|user.type |string |链接创建者的类型 | +|user.name |string |链接创建者的名称 | +|user.login |string |链接创建者的标识 | +|user.image_url |string |链接创建者头像 | +|project.id |int |链接关联项目的id | +|project.identifier |string |链接关联项目的标识 | +|project.name |string |链接关联项目的名称 | +|project.description |string |链接关联项目的描述 | +|project.is_public |bool |链接关联项目是否公开 | +|project.owner.id |bool |链接关联项目拥有者id | +|project.owner.type |string |链接关联项目拥有者类型 | +|project.owner.name |string |链接关联项目拥有者昵称 | +|project.owner.login |string |链接关联项目拥有者标识 | +|project.owner.image_url|string |链接关联项目拥有者头像 | + +> 返回的JSON示例: + +```json +{ + "id": 7, + "role": "developer", + "is_apply": false, + "sign": "6b6b454843c291d4e52e60853cb8ad9f", + "user": { + "id": 2, + "type": "User", + "name": "heh", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png" + }, + "project": { + "id": 474, + "identifier": "kellect", + "name": "kellect", + "description": null, + "is_public": true, + "owner": { + "id": 2, + "type": "User", + "name": "heh", + "login": "yystopf", + "image_url": "system/lets/letter_avatars/2/H/188_239_142/120.png" + } + } +} +``` + +## 请求项目邀请链接 +当前登录(非项目)用户加入项目的接口,如果项目链接不需要审核,请求成功后即加入项目,如果需要审核,那么会提交一个申请,需要项目管理员审核 + +> 示例: + +```shell +curl -X POST http://localhost:3000/api/yystopf/kellect/project_invite_links/redirect_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6 +``` + +```javascript +await octokit.request('POST /api/yystopf/kellect/project_invite_links/redirect_link.json?invite_sign=d612df03aad63760445c187bcf83f2e6') +``` + +### HTTP 请求 +`POST /api/:owner/:repo/project_invite_links/generate_link.json?invite_sign=xxx` + +### 请求参数 +参数 | 必选 | 默认 | 类型 | 字段说明 +--------- | ------- | ------- | -------- | ---------- +|invite_sign |是| |string |项目邀请链接的标识 | + +> 返回的JSON示例: + +```json +{ + "status": 0, + "message": "success" +} +``` ## 申请加入项目 申请加入项目 diff --git a/app/services/projects/link_join_service.rb b/app/services/projects/link_join_service.rb index c42376ef..e586933f 100644 --- a/app/services/projects/link_join_service.rb +++ b/app/services/projects/link_join_service.rb @@ -32,7 +32,7 @@ class Projects::LinkJoinService < ApplicationService end def create_applied_project! - user.applied_projects.create!(project: project, role: role_value) + user.applied_projects.create!(project: project, role: role_value, project_invite_link_id: invite_link&.id) end def create_member! diff --git a/app/views/projects/project_invite_links/generate_link.json.jbuilder b/app/views/projects/project_invite_links/generate_link.json.jbuilder new file mode 100644 index 00000000..1903e10a --- /dev/null +++ b/app/views/projects/project_invite_links/generate_link.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'detail', locals: { project_invite_link: @project_invite_link } diff --git a/public/docs/api.html b/public/docs/api.html index 8ef6924b..bd3f49eb 100644 --- a/public/docs/api.html +++ b/public/docs/api.html @@ -425,6 +425,15 @@
  • Projects