diff --git a/.gitignore b/.gitignore
index d0186c4a3..6af1a6cd4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
# Ignore lock config file
*.log
+.rubocop.yml
.env
# mac
*.DS_Store
diff --git a/Gemfile b/Gemfile
index 4e7d9d77b..648dc2e92 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,6 @@
#source 'https://gems.ruby-china.com'
source 'https://mirrors.cloud.tencent.com/rubygems/'
+#source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '~> 5.2.0'
@@ -26,7 +27,7 @@ gem 'roo-xls'
gem 'simple_xlsx_reader', '~>1.0.4'
gem 'rubyzip'
-
+gem 'sonarqube', :git => 'https://gitlink.org.cn/KingChan/sonarqube.git'
gem 'spreadsheet'
gem 'ruby-ole'
# 导出为xlsx
@@ -70,6 +71,7 @@ group :development do
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'spring'
+ gem 'pry-rails'
gem 'spring-watcher-listen', '~> 2.0.0'
gem "annotate", "~> 2.6.0"
end
@@ -114,7 +116,6 @@ gem 'aasm'
gem 'enumerize'
gem 'diffy'
-
gem 'deep_cloneable', '~> 3.0.0'
# oauth2
@@ -141,4 +142,6 @@ gem 'doorkeeper'
gem 'doorkeeper-jwt'
-gem 'gitea-client', '~> 1.5.8'
+gem 'gitea-client', '~> 1.6.1'
+
+gem 'loofah', '~> 2.20.0'
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 838873f31..34f5318d7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,3 +1,11 @@
+GIT
+ remote: https://gitlink.org.cn/KingChan/sonarqube.git
+ revision: 80f07d427322ef02c0714c77a382e87aed0bef81
+ specs:
+ sonarqube (1.3.0)
+ httparty (~> 0.14, >= 0.14.0)
+ terminal-table (~> 1.5, >= 1.5.1)
+
GEM
remote: https://mirrors.cloud.tencent.com/rubygems/
specs:
@@ -135,7 +143,7 @@ GEM
fugit (1.4.1)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.4)
- gitea-client (1.4.2)
+ gitea-client (1.4.6)
rest-client (~> 2.1.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
@@ -150,6 +158,9 @@ GEM
http-accept (1.7.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
+ httparty (0.21.0)
+ mini_mime (>= 1.0.0)
+ multi_xml (>= 0.5.2)
i18n (1.8.2)
concurrent-ruby (~> 1.0)
io-like (0.3.1)
@@ -187,9 +198,9 @@ GEM
mimemagic (~> 0.3.2)
maruku (0.7.3)
method_source (0.9.2)
- mime-types (3.4.1)
+ mime-types (3.5.2)
mime-types-data (~> 3.2015)
- mime-types-data (3.2023.0218.1)
+ mime-types-data (3.2024.0507)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
@@ -437,6 +448,8 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
thor (1.0.1)
thread_safe (0.3.6)
tilt (2.0.10)
@@ -449,7 +462,7 @@ GEM
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
- unf_ext (0.0.8.2)
+ unf_ext (0.0.9.1)
unicode-display_width (1.6.1)
web-console (3.7.0)
actionview (>= 5.0)
@@ -491,7 +504,7 @@ DEPENDENCIES
enumerize
faraday (~> 0.15.4)
font-awesome-sass (= 4.7.0)
- gitea-client (~> 1.4.2)
+ gitea-client (~> 1.4.3)
grape-entity (~> 0.7.1)
groupdate (~> 4.1.0)
harmonious_dictionary (~> 0.0.1)
@@ -539,6 +552,7 @@ DEPENDENCIES
simple_xlsx_reader (~> 1.0.4)
sinatra
solargraph (~> 0.38.0)
+ sonarqube!
spreadsheet
spring
spring-watcher-listen (~> 2.0.0)
diff --git a/app/assets/javascripts/api/pm/issue_links.js b/app/assets/javascripts/api/pm/issue_links.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/api/pm/issue_links.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/api/pm/projects.js b/app/assets/javascripts/api/pm/projects.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/api/pm/projects.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/api/v1/pm_issues.js b/app/assets/javascripts/api/v1/pm_issues.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/api/v1/pm_issues.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/api/v1/sonarqube/issues.js b/app/assets/javascripts/api/v1/sonarqube/issues.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/api/v1/sonarqube/issues.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/api/v1/sonarqubes.js b/app/assets/javascripts/api/v1/sonarqubes.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/api/v1/sonarqubes.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/pm/journals.js b/app/assets/javascripts/pm/journals.js
new file mode 100644
index 000000000..dee720fac
--- /dev/null
+++ b/app/assets/javascripts/pm/journals.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
index 8719d767e..7b0ee0300 100644
--- a/app/assets/stylesheets/admin.scss
+++ b/app/assets/stylesheets/admin.scss
@@ -40,6 +40,13 @@ body {
}
}
}
+.editormd .CodeMirror{
+ margin-top: 35px!important;
+}
+
+.CodeMirror-gutter .CodeMirror-linenumbers {
+ width: 28px!important;
+}
input.form-control {
font-size: 14px;
diff --git a/app/assets/stylesheets/api/pm/issue_links.scss b/app/assets/stylesheets/api/pm/issue_links.scss
new file mode 100644
index 000000000..730f1f3e1
--- /dev/null
+++ b/app/assets/stylesheets/api/pm/issue_links.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the api/pm/issue_links controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/api/pm/projects.scss b/app/assets/stylesheets/api/pm/projects.scss
new file mode 100644
index 000000000..7053c94f2
--- /dev/null
+++ b/app/assets/stylesheets/api/pm/projects.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the api/pm/projects controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/api/v1/pm_issues.scss b/app/assets/stylesheets/api/v1/pm_issues.scss
new file mode 100644
index 000000000..92defb491
--- /dev/null
+++ b/app/assets/stylesheets/api/v1/pm_issues.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the api/v1/pm_issues controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/api/v1/sonarqube/issues.scss b/app/assets/stylesheets/api/v1/sonarqube/issues.scss
new file mode 100644
index 000000000..a74cba738
--- /dev/null
+++ b/app/assets/stylesheets/api/v1/sonarqube/issues.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the api/v1/sonarqube/issues controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/api/v1/sonarqubes.scss b/app/assets/stylesheets/api/v1/sonarqubes.scss
new file mode 100644
index 000000000..8b651fe3a
--- /dev/null
+++ b/app/assets/stylesheets/api/v1/sonarqubes.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the api/v1/sonarqubes controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/pm/journals.scss b/app/assets/stylesheets/pm/journals.scss
new file mode 100644
index 000000000..45dbf18b4
--- /dev/null
+++ b/app/assets/stylesheets/pm/journals.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the pm/journals controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 14d59af77..119812225 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -1,6 +1,7 @@
class AccountsController < ApplicationController
- before_action :require_login, only: [:login_check, :simple_update]
+ before_action :require_login, only: [:login_check, :simple_update, :change_password]
include ApplicationHelper
+ include AesCryptHelper
#skip_before_action :check_account, :only => [:logout]
@@ -143,7 +144,8 @@ class AccountsController < ApplicationController
user = Users::RegisterService.call(register_params)
user.mail = "#{user.login}@example.org" if user.mail.blank?
- password = register_params[:password].strip
+ password = decrypt(register_params[:password]) rescue ""
+ password = password.strip
# gitea用户注册, email, username, password
interactor = Gitea::RegisterInteractor.call({username: user.login, email: user.mail, password: password})
@@ -193,8 +195,9 @@ class AccountsController < ApplicationController
# 用户登录
def login
- Users::LoginForm.new(login_params).validate!
- @user = User.try_to_login(params[:login], params[:password])
+ password = decrypt(login_params[:password]) rescue ""
+ Users::LoginForm.new(login_params.merge!({password: password})).validate!
+ @user = User.try_to_login(params[:login], password)
return normal_status(-2, "错误的账号或密码") if @user.blank?
# user is already in local database
@@ -203,7 +206,7 @@ class AccountsController < ApplicationController
login_control = LimitForbidControl::UserLogin.new(@user)
return normal_status(-2, "登录密码出错已达上限,账号已被锁定,请#{login_control.forbid_expires/60}分钟后重新登录或找回密码") if login_control.forbid?
- password_ok = @user.check_password?(params[:password].to_s)
+ password_ok = @user.check_password?(password.to_s)
unless password_ok
if login_control.remain_times-1 == 0
normal_status(-2, "登录密码出错已达上限,账号已被锁定,请#{login_control.forbid_expires/60}分钟后重新登录或找回密码")
@@ -216,20 +219,24 @@ class AccountsController < ApplicationController
LimitForbidControl::UserLogin.new(@user).clear
successful_authentication(@user)
- sync_pwd_to_gitea!(@user, {password: params[:password].to_s}) # TODO用户密码未同步
+ sync_pwd_to_gitea!(@user, {password: password.to_s}) # TODO用户密码未同步
# session[:user_id] = @user.id
end
def change_password
- return render_error("两次输入的密码不一致") if params[:password].to_s != params[:new_password_repeat].to_s
+ password = decrypt(params[:password]) rescue ""
+ new_password_repeat = decrypt(params[:new_password_repeat]) rescue ""
+ old_password = decrypt(params[:old_password]) rescue ""
+ return render_error("两次输入的密码不一致") if password.to_s != new_password_repeat.to_s
@user = User.find_by(login: params[:login])
+ return render_forbidden unless User.current.login == @user&.login
return render_error("此用户禁止修改密码!") if @user.id.to_i === 104691
return render_error("未找到相关用户!") if @user.blank?
- return render_error("旧密码不正确") unless @user.check_password?(params[:old_password])
+ return render_error("旧密码不正确") unless @user.check_password?(old_password)
sync_params = {
- password: params[:password].to_s,
+ password: password.to_s,
email: @user.mail,
login_name: @user.name,
source_id: 0
@@ -237,7 +244,7 @@ class AccountsController < ApplicationController
interactor = Gitea::User::UpdateInteractor.call(@user.login, sync_params)
if interactor.success?
- @user.update_attribute(:password, params[:password])
+ @user.update_attribute(:password, password)
render_ok
else
render_error(interactor.error)
diff --git a/app/controllers/action/node_inputs_controller.rb b/app/controllers/action/node_inputs_controller.rb
index 65227c657..cf108f324 100644
--- a/app/controllers/action/node_inputs_controller.rb
+++ b/app/controllers/action/node_inputs_controller.rb
@@ -6,7 +6,7 @@ class Action::NodeInputsController < ApplicationController
@node_inputs = @node.action_node_inputs
respond_to do |format|
format.html
- format.json
+ format.json{ render_ok(data: @node_inputs.as_json) }
end
end
diff --git a/app/controllers/action/node_types_controller.rb b/app/controllers/action/node_types_controller.rb
index 32508d942..e90bb665c 100644
--- a/app/controllers/action/node_types_controller.rb
+++ b/app/controllers/action/node_types_controller.rb
@@ -4,6 +4,10 @@ class Action::NodeTypesController < ApplicationController
def index
@node_types = Action::NodeType.all
+ respond_to do |format|
+ format.html
+ format.json { render_ok(data: @node_types.as_json) }
+ end
end
def create
@@ -20,7 +24,10 @@ class Action::NodeTypesController < ApplicationController
end
def show
-
+ respond_to do |format|
+ format.html
+ format.json { render_ok(data: @node_type.as_json) }
+ end
end
def new
@@ -45,7 +52,10 @@ class Action::NodeTypesController < ApplicationController
else
flash[:danger] = '删除失败'
end
- redirect_to action_node_types_path
+ respond_to do |format|
+ format.html { redirect_to action_node_types_path }
+ format.json { render_ok }
+ end
end
private
diff --git a/app/controllers/action/nodes_controller.rb b/app/controllers/action/nodes_controller.rb
index e1e7799f4..67eb70c2f 100644
--- a/app/controllers/action/nodes_controller.rb
+++ b/app/controllers/action/nodes_controller.rb
@@ -1,18 +1,25 @@
class Action::NodesController < ApplicationController
- before_action :require_admin, except: [:index]
+ # before_action :require_admin, except: [:index]
+ before_action :require_login
before_action :find_action_node, except: [:index, :create, :new]
def index
@node_types = Action::NodeType.all
+ no_node_type = Action::NodeType.find_by(name: "未分类")
@no_type_nodes = Action::Node.where(action_node_types_id: nil)
+ @no_type_nodes = Action::Node.where(action_node_types_id: nil).or(Action::Node.where(action_node_types_id: no_node_type.id)) if no_node_type.present?
respond_to do |format|
- format.html { @nodes = Action::Node.all }
+ format.html { @nodes = Action::Node.where("name LIKE :search OR full_name LIKE :search", :search => "%#{params[:search]}%") }
format.json
end
end
def create
@node = Action::Node.new(node_params)
+ if params.require(:node).present? && params.require(:node)[:link_type_array].present?
+ @node.link_type = (params.require(:node)[:link_type_array] - [""]).join(",")
+ end
+ @node.user_id = current_user.id
respond_to do |format|
if @node.save
format.html { redirect_to action_nodes_path, notice: '创建成功.' }
@@ -33,10 +40,16 @@ class Action::NodesController < ApplicationController
end
def edit
-
+ if @node.link_type.present?
+ @node.link_type_array = @node.link_type.to_s.split(",")
+ end
end
def update
+ if params.require(:node).present? && params.require(:node)[:link_type_array].present?
+ @node.link_type = (params.require(:node)[:link_type_array] - [""]).join(",")
+ end
+ @node.user_id = current_user.id if @node.user_id.blank?
@node.update(node_params)
respond_to do |format|
format.html { redirect_to action_nodes_path, notice: '更新成功.' }
@@ -50,7 +63,10 @@ class Action::NodesController < ApplicationController
else
flash[:danger] = '删除失败'
end
- redirect_to action_nodes_path
+ respond_to do |format|
+ format.html { redirect_to action_nodes_path }
+ format.json { render_ok() }
+ end
end
private
@@ -61,9 +77,11 @@ class Action::NodesController < ApplicationController
def node_params
if params.require(:action_node)
- params.require(:action_node).permit(:name, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
+ params.require(:action_node).permit(:name, :label, :full_name, :description, :icon, :action_node_types_id,
+ :is_local, :local_url, :yaml, :sort_no, :node_type, :is_mutil_link, :link_type, :link_type_array)
else
- params.permit(:name, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no)
+ params.permit(:name, :label, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url,
+ :yaml, :sort_no, :node_type, :is_mutil_link, :link_type, :link_type_array)
end
end
end
diff --git a/app/controllers/action/templates_controller.rb b/app/controllers/action/templates_controller.rb
index 092d38d64..f7f99b7d7 100644
--- a/app/controllers/action/templates_controller.rb
+++ b/app/controllers/action/templates_controller.rb
@@ -49,7 +49,10 @@ class Action::TemplatesController < ApplicationController
else
flash[:danger] = '删除失败'
end
- redirect_to action_templates_path
+ respond_to do |format|
+ format.html { redirect_to action_templates_path }
+ format.json { render_ok }
+ end
end
private
diff --git a/app/controllers/admins/dashboards_controller.rb b/app/controllers/admins/dashboards_controller.rb
index de7b6ca93..325a980ce 100644
--- a/app/controllers/admins/dashboards_controller.rb
+++ b/app/controllers/admins/dashboards_controller.rb
@@ -73,26 +73,26 @@ class Admins::DashboardsController < Admins::BaseController
@subject_icon = ["fa-user","fa-git", "fa-sitemap", "fa-warning", "fa-comments", "fa-share-alt", "fa-upload"]
@subject_data = [@user_count, @project_count, @organization_count, @issue_count, @comment_count, @pr_count, @commit_count]
-
- tongji_service = Baidu::TongjiService.new
- @access_token = tongji_service.access_token
- Rails.logger.info "baidu_tongji_auth access_token ===== #{@access_token}"
- # @overview_data = tongji_service.api_overview
- last_date = DailyPlatformStatistic.order(:date).last
- start_date = last_date.date
- end_date = Time.now
- if @access_token.present?
- @overview_data = Rails.cache.fetch("dashboardscontroller:baidu_tongji:overview_data", expires_in: 10.minutes) do
- tongji_service.source_from_batch_add(start_date, end_date)
- @overview_data = tongji_service.overview_batch_add(start_date, end_date)
- @overview_data
+ if EduSetting.get("open_baidu_tongji").to_s == "true"
+ tongji_service = Baidu::TongjiService.new
+ @access_token = tongji_service.access_token
+ Rails.logger.info "baidu_tongji_auth access_token ===== #{@access_token}"
+ # @overview_data = tongji_service.api_overview
+ last_date = DailyPlatformStatistic.order(:date).last || Time.now
+ start_date = last_date.date
+ end_date = Time.now
+ if @access_token.present?
+ @overview_data = Rails.cache.fetch("dashboardscontroller:baidu_tongji:overview_data", expires_in: 10.minutes) do
+ tongji_service.source_from_batch_add(start_date, end_date)
+ @overview_data = tongji_service.overview_batch_add(start_date, end_date)
+ @overview_data
+ end
end
+
+ @current_week_statistic = DailyPlatformStatistic.where(date: current_week)
+ @pre_week_statistic = DailyPlatformStatistic.where(date: pre_week)
end
- @current_week_statistic = DailyPlatformStatistic.where(date: current_week)
- @pre_week_statistic = DailyPlatformStatistic.where(date: pre_week)
-
-
end
diff --git a/app/controllers/admins/nps_controller.rb b/app/controllers/admins/nps_controller.rb
index bfb72f730..2817cd0c6 100644
--- a/app/controllers/admins/nps_controller.rb
+++ b/app/controllers/admins/nps_controller.rb
@@ -2,7 +2,7 @@ class Admins::NpsController < Admins::BaseController
before_action :require_business
def index
@on_off_switch = EduSetting.get("nps-on-off-switch").to_s == 'true'
- @user_nps = UserNp.joins(:user).order(created_at: :desc)
+ @user_nps = UserNp.order(created_at: :desc)
keyword = params[:keyword].to_s.strip.presence
if keyword
sql = 'CONCAT(users.lastname, users.firstname) LIKE :keyword OR users.nickname LIKE :keyword OR users.login LIKE :keyword OR users.mail LIKE :keyword OR users.phone LIKE :keyword'
diff --git a/app/controllers/admins/projects_controller.rb b/app/controllers/admins/projects_controller.rb
index f5f210a0d..79582cb7c 100644
--- a/app/controllers/admins/projects_controller.rb
+++ b/app/controllers/admins/projects_controller.rb
@@ -6,7 +6,7 @@ class Admins::ProjectsController < Admins::BaseController
sort_by = Project.column_names.include?(params[:sort_by]) ? params[:sort_by] : 'created_on'
sort_direction = %w(desc asc).include?(params[:sort_direction]) ? params[:sort_direction] : 'desc'
search = params[:search].to_s.strip
- projects = Project.where("name like ? OR identifier LIKE ?", "%#{search}%", "%#{search}%").order("#{sort_by} #{sort_direction}")
+ projects = Project.where("id = ? OR name like ? OR identifier LIKE ?", search, "%#{search}%", "%#{search}%").order("#{sort_by} #{sort_direction}")
case params[:category]
when 'public'
projects = projects.where(is_public: true)
diff --git a/app/controllers/admins/user_actions_controller.rb b/app/controllers/admins/user_actions_controller.rb
new file mode 100644
index 000000000..ff52d5a1a
--- /dev/null
+++ b/app/controllers/admins/user_actions_controller.rb
@@ -0,0 +1,14 @@
+class Admins::UserActionsController < Admins::BaseController
+ before_action :require_admin
+
+ def index
+ @user_actions = UserAction.order(created_at: :desc)
+ @user_actions = @user_actions.where(action_type: params[:action_type]) if params[:action_type].present?
+ keyword = params[:keyword].to_s.strip.presence
+ if keyword
+ sql = 'login LIKE :keyword OR phone LIKE :keyword OR email LIKE :keyword'
+ @user_actions = @user_actions.where(sql, keyword: "%#{keyword}%")
+ end
+ @user_actions = paginate @user_actions
+ end
+end
diff --git a/app/controllers/admins/users_controller.rb b/app/controllers/admins/users_controller.rb
index e544cfb8e..f552c9cd1 100644
--- a/app/controllers/admins/users_controller.rb
+++ b/app/controllers/admins/users_controller.rb
@@ -27,10 +27,30 @@ class Admins::UsersController < Admins::BaseController
def destroy
UserAction.create(action_id: @user.id, action_type: "DestroyUser", user_id: current_user.id, :ip => request.remote_ip, data_bank: @user.attributes.to_json)
- @user.destroy!
- Gitea::User::DeleteService.call(@user.login)
-
- render_delete_success
+ # org_ids = TeamUser.where(user_id: @user.id).pluck(:organization_id) | OrganizationUser.where(user_id: @user.id).pluck(:organization_id)
+ # organizations = Organization.where(id: org_ids)
+ # organizations.each do |org|
+ # # org.team_users.joins(:team).where(user_id: @user.id, teams: {authorize: %w(owner)})
+ # owner_count = org.team_users.joins(:team).where(teams: {authorize: %w(owner)}).count
+ # # 多个owner时,仅将用户从组织移除, 一个时直接删除
+ # if owner_count > 1
+ # org.team_users.joins(:team).where(user_id: @user.id, teams: {authorize: %w(owner)}).destroy_all
+ # org.organization_users.where(user_id: @user.id, organization_id: org.id).destroy_all
+ # else
+ # org.destroy
+ # end
+ # end
+ # @user.destroy!
+ # Gitea::User::DeleteService.call(@user.login, true)
+ #
+ # render_delete_success
+
+ @result_object = Api::V1::Users::DeleteUserService.call(@user)
+ if @result_object
+ render_delete_success
+ else
+ render_js_error('删除失败!')
+ end
end
def lock
diff --git a/app/controllers/api/pm/action_runs_controller.rb b/app/controllers/api/pm/action_runs_controller.rb
new file mode 100644
index 000000000..85af3a447
--- /dev/null
+++ b/app/controllers/api/pm/action_runs_controller.rb
@@ -0,0 +1,45 @@
+class Api::Pm::ActionRunsController < Api::Pm::BaseController
+ before_action :require_login
+
+ def index
+ tip_exception('请输入workflows') if params[:workflows].blank?
+ @owner = Owner.find_by(login: params[:owner_id].to_s) || Owner.find_by(id: params[:owner_id].to_s)
+ tip_exception('组织未找到') if @owner.blank?
+ action_runs = Gitea::ActionRun.where(owner_id: @owner.gitea_uid)
+ group_data = action_runs.where(workflow_id: params[:workflows].to_s.split(",")).where(status: [1,2]).group(:workflow_id, :status).count
+ @result = []
+ params[:workflows].to_s.split(",").each do |file|
+ last_action_run = action_runs.where(workflow_id: file).order(updated: :desc).first
+ last_action_run_json = last_action_run.present? ? {
+ id: last_action_run.id,
+ schedule: last_action_run.schedule_id > 0,
+ title: last_action_run.title,
+ index: last_action_run.index,
+ status: last_action_run.status,
+ started: last_action_run.started,
+ stopped: last_action_run.stopped,
+ length: last_action_run.stopped - last_action_run.started,
+ created: last_action_run.created,
+ updated: last_action_run.updated,
+ } : {}
+
+ total = 0
+ success = 0
+ failure = 0
+ group_data.each do |k,v|
+ total += v if k[0] == file
+ success += v if k[0] == file && k[1] == 1
+ failure += v if k[0] == file && k[1] == 2
+ end
+
+ @result << {
+ name: file,
+ total: total,
+ success: success,
+ failure: failure
+ }.merge(last_action_run_json)
+ end
+ render :json => {data: @result}
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/pm/base_controller.rb b/app/controllers/api/pm/base_controller.rb
new file mode 100644
index 000000000..f9754f33d
--- /dev/null
+++ b/app/controllers/api/pm/base_controller.rb
@@ -0,0 +1,60 @@
+class Api::Pm::BaseController < ApplicationController
+
+ include Api::ProjectHelper
+ include Api::UserHelper
+ include Api::PullHelper
+
+ # before_action :doorkeeper_authorize!
+ # skip_before_action :user_setup
+
+ protected
+
+ def kaminary_select_paginate(relation)
+ limit = params[:limit] || params[:per_page]
+ limit = (limit.to_i.zero? || limit.to_i > 200) ? 200 : limit.to_i
+ page = params[:page].to_i.zero? ? 1 : params[:page].to_i
+
+ relation.page(page).per(limit)
+ end
+
+ def limit
+ params.fetch(:limit, 15)
+ end
+
+ def page
+ params.fetch(:page, 1)
+ end
+
+ def load_project
+ @project = Project.find_by_id(params[:project_id]) || Project.new(id: 0, user_id: 0, name: 'pm_mm', identifier: 'pm_mm', is_public:true)
+ end
+
+ def load_issue
+ return render_parameter_missing if params[:pm_project_id].blank?
+ @issue = Issue.issue_issue.where(pm_project_id: params[:pm_project_id]).find_by_id(params[:issue_id])
+ render_not_found('疑修不存在!') if @issue.blank?
+ end
+ # 具有对仓库的管理权限
+ def require_manager_above
+ @project = load_project
+ return render_forbidden if !current_user.admin? && !@project.manager?(current_user)
+ end
+
+ # 具有对仓库的操作权限
+ def require_operate_above
+ @project = load_project
+ return render_forbidden if !current_user.admin? && !@project.operator?(current_user)
+ end
+
+ # 具有仓库的操作权限或者fork仓库的操作权限
+ def require_operate_above_or_fork_project
+ @project = load_project
+ return render_forbidden if !current_user.admin? && !@project.operator?(current_user) && !(@project.fork_project.present? && @project.fork_project.operator?(current_user))
+ end
+
+ # 具有对仓库的访问权限
+ def require_public_and_member_above
+ @project = load_project
+ return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/api/pm/dashboards_controller.rb b/app/controllers/api/pm/dashboards_controller.rb
new file mode 100644
index 000000000..2798c3c52
--- /dev/null
+++ b/app/controllers/api/pm/dashboards_controller.rb
@@ -0,0 +1,137 @@
+class Api::Pm::DashboardsController < Api::Pm::BaseController
+ before_action :require_login
+ def index
+ end
+
+ def todo
+ return render_error('请输入正确的pm_project_ids.') if params[:pm_project_ids].blank?
+ pm_project_ids = params[:pm_project_ids].split(",") rescue []
+ date = params[:date].present? ? params[:date].to_date : Date.today rescue Date.today
+ @issues = Issue.where("start_date <= ? and due_date >= ?", date, date)
+ @issues = @issues.where(pm_project_id: pm_project_ids).joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'})
+ @issues = @issues.where.not(status_id: 5)
+ @issues = kaminari_paginate(@issues.distinct.pm_includes)
+ end
+
+ def my_issues
+ return render_error('请输入正确的pm_project_ids.') if params[:pm_project_ids].blank?
+ return render_error('请输入正确的pm_issue_types.') if params[:pm_issue_types].blank?
+ pm_project_ids = params[:pm_project_ids].split(",") rescue []
+ pm_issue_types = params[:pm_issue_types].split(",") rescue []
+ @all_issues = Issue.where(pm_project_id: pm_project_ids, pm_issue_type: pm_issue_types)
+ @issues = @all_issues.joins(:issue_participants).where(issue_participants: {participant_id: current_user.id})
+
+ @issues = kaminari_paginate(@issues.distinct.pm_includes)
+ @my_assign_requirements_count = @all_issues.where(pm_issue_type: 1).joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'}).size
+ @my_assign_tasks_count = @all_issues.where(pm_issue_type: 2).joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'}).size
+ @my_assign_bugs_count = @all_issues.where(pm_issue_type: 3).joins(:issue_participants).where(issue_participants: {participant_id: current_user.id, participant_type: 'assigned'}).size
+ end
+
+ def my_pm_projects
+ return render_error('请输入正确的pm_project_id.') if params[:pm_project_id].blank?
+ @all_issues = Issue.where(pm_project_id: params[:pm_project_id])
+ time_now = Time.now
+ @last_week_create_issues_count = @all_issues.where("created_on > ? and created_on < ?", time_now - 7.days, time_now).size
+ @before_last_week_create_issue_count = @all_issues.where("created_on > ? and created_on < ?", time_now - 14.days, time_now - 7.days).size
+ @compare_last_week_create_issues = @before_last_week_create_issue_count.zero? ? 0 :(@last_week_create_issues_count - @before_last_week_create_issue_count).to_f / @before_last_week_create_issue_count rescue 0
+ @last_week_close_issues_count = @all_issues.where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 7.days, time_now).size
+ @before_last_week_close_issue_count = @all_issues.where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 14.days, time_now - 7.days).size
+ @compare_last_week_close_issues = @before_last_week_close_issue_count.zero? ? 0 :(@last_week_close_issues_count - @before_last_week_close_issue_count).to_f / @before_last_week_close_issue_count rescue 0
+ @all_requirement_issues_count = @all_issues.where(pm_issue_type: 1).size
+ @open_requirement_issues_count = @all_issues.where(pm_issue_type: 1).where.not(status_id: 5).size
+ @last_week_close_requirement_issues_count = @all_issues.where(pm_issue_type: 1).where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 7.days, time_now).size
+ @last_month_close_requirement_issues_count = @all_issues.where(pm_issue_type: 1).where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 30.days, time_now).size
+ @all_task_issues_count = @all_issues.where(pm_issue_type: 2).size
+ @open_task_issues_count = @all_issues.where(pm_issue_type: 2).where.not(status_id: 5).size
+ @last_week_close_tast_issues_count = @all_issues.where(pm_issue_type: 2).where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 7.days, time_now).size
+ @last_month_close_task_issues_count = @all_issues.where(pm_issue_type: 2).where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 30.days, time_now).size
+ @all_bug_issues_count = @all_issues.where(pm_issue_type: 3).size
+ @open_bug_issues_count = @all_issues.where(pm_issue_type: 3).where.not(status_id: 5).size
+ @last_week_close_bug_issues_count = @all_issues.where(pm_issue_type: 3).where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 7.days, time_now).size
+ @last_month_close_bug_issues_count = @all_issues.where(pm_issue_type: 3).where(status_id: 5).where("updated_on > ? and updated_on < ?", time_now - 30.days, time_now).size
+
+
+ @requirement_close_trend = [[],[]]
+ @task_close_trend = [[],[]]
+ @bug_close_trend = [[],[]]
+ ((time_now-29.days).to_date..time_now.to_date).to_a.each do |i|
+ @requirement_close_trend[0] << i.strftime("%Y.%m.%d")
+ @task_close_trend[0] << i.strftime("%Y.%m.%d")
+ @bug_close_trend[0] << i.strftime("%Y.%m.%d")
+ @requirement_close_trend[1] << @all_issues.where(pm_issue_type: 1, status_id: 5).where("DATE(updated_on) = ?", i).size
+ @task_close_trend[1] << @all_issues.where(pm_issue_type: 2, status_id: 5).where("DATE(updated_on) = ?", i).size
+ @bug_close_trend[1] << @all_issues.where(pm_issue_type: 3, status_id: 5).where("DATE(updated_on) = ?", i).size
+ end
+ @close_trend = {requirement: @requirement_close_trend, task: @task_close_trend, bug: @bug_close_trend}
+
+ render_ok(data: {
+ last_week_close_issues_count: @last_week_close_issues_count,
+ before_last_week_close_issue_count: @before_last_week_close_issue_count,
+ compare_last_week_close_issues: @compare_last_week_close_issues,
+ last_week_create_issues_count: @last_week_create_issues_count,
+ before_last_week_create_issue_count: @before_last_week_create_issue_count,
+ compare_last_week_create_issues: @compare_last_week_create_issues,
+ all_requirement_issues_count: @all_requirement_issues_count,
+ open_requirement_issues_count: @open_requirement_issues_count,
+ last_week_close_requirement_issues_count: @last_week_close_requirement_issues_count,
+ last_month_close_requirement_issues_count: @last_month_close_requirement_issues_count,
+ all_task_issues_count: @all_task_issues_count,
+ open_task_issues_count: @open_task_issues_count,
+ last_week_close_task_issues_count: @last_week_close_tast_issues_count,
+ last_month_close_task_issues_count: @last_month_close_task_issues_count,
+ all_bug_issues_count: @all_bug_issues_count,
+ open_bug_issues_count: @open_bug_issues_count,
+ last_week_close_bug_issues_count: @last_week_close_bug_issues_count,
+ last_month_close_bug_issues_count: @last_month_close_bug_issues_count,
+ close_trend: @close_trend
+ })
+ end
+
+ def my_projects
+ return render_error('请输入正确的project_id.') if params[:project_id].blank?
+
+ @project = Project.find_by_id params[:project_id]
+ return render_error('请输入正确的project_id.') unless @project.present?
+ time_now = Time.now
+
+ branch_tag_result = $gitea_hat_client.get_repos_branch_tag_count_by_owner_repo(@project&.owner&.login, @project&.identifier) rescue {}
+ languages_result = $gitea_client.get_repos_languages_by_owner_repo(@project&.owner&.login, @project&.identifier) rescue {}
+
+ @open_pull_requests_count = @project.pull_requests.opening.size
+ @last_week_close_pull_requests_count = @project.pull_requests.where(status: 1).where("updated_at > ? and updated_at < ?", time_now - 7.days, time_now).size
+ @last_month_close_pull_requets_count = @project.pull_requests.where(status: 1).where("updated_at > ? and updated_at < ?", time_now - 30.days, time_now).size
+
+ @commits_count = @project.commit_logs.size
+ @last_week_commits_count = @project.commit_logs.where("created_at > ? and created_at < ?", time_now - 7.days, time_now).size
+ @last_month_commits_count = @project.commit_logs.where("created_at > ? and created_at < ?", time_now - 30.days, time_now).size
+
+ render_ok(data: {
+ branch_count: branch_tag_result["branch_count"].to_i,
+ tag_count: branch_tag_result["tag_count"].to_i,
+ license_name: @project.license&.name,
+ open_pull_requests_count: @open_pull_requests_count,
+ last_week_close_pull_requests_count: @last_week_close_pull_requests_count,
+ last_month_close_pull_requets_count: @last_month_close_pull_requets_count,
+ commits_count: @commits_count,
+ last_week_commits_count: @last_week_commits_count,
+ last_month_commits_count: @last_month_commits_count,
+ language: hash_transform_precentagable(languages_result),
+ })
+ end
+
+ def my_operate_journals
+ return render_error('请输入正确的pm_project_id.') if params[:pm_project_id].blank?
+ @journals = Journal.operate_journals.joins(:issue).where(issues: {pm_project_id: params[:pm_project_id], pm_issue_type: [1,2,3]})
+
+ @journals = kaminari_paginate(@journals.order(updated_on: :desc))
+ end
+
+ private
+ def hash_transform_precentagable(hash)
+ total_byte_size = hash.values.sum
+ hash.transform_values { |v|
+ ActionController::Base.helpers
+ .number_to_percentage((v * 100.0 / total_byte_size), precision: 1)
+ }.select{|k,v| v != "0.0%"}
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/api/pm/issue_links_controller.rb b/app/controllers/api/pm/issue_links_controller.rb
new file mode 100644
index 000000000..867dcaf41
--- /dev/null
+++ b/app/controllers/api/pm/issue_links_controller.rb
@@ -0,0 +1,46 @@
+class Api::Pm::IssueLinksController < Api::Pm::BaseController
+ before_action :load_project
+ before_action :load_issue
+ def index
+ @links = PmLink.where(be_linkable_id: @issue.id,be_linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id,linkable_type: 'Issue'))
+ end
+
+ def create
+ begin
+ ActiveRecord::Base.transaction do
+ params[:link_ids].each do |e|
+ @issue.pm_links.find_or_create_by!(be_linkable_type: 'Issue', be_linkable_id: e)
+ tag_issue = Issue.find_by_id(e)
+ next unless tag_issue.present?
+ journal = tag_issue.journals.create!({user_id: current_user.id})
+ journal.journal_details.create!({property: "tag_link_issue", prop_key: "1", value: @issue.id.to_s})
+ end
+ journal = @issue.journals.create!({user_id: current_user.id})
+ journal.journal_details.create!({property: "tag_link_issue", prop_key: "#{params[:link_ids].size}", value: params[:link_ids].join(",")})
+ end
+ render_ok
+ rescue
+ render_error('创建失败!')
+ end
+ end
+
+ def destroy
+ begin
+ ActiveRecord::Base.transaction do
+ @links = PmLink.where(be_linkable_id: @issue.id, be_linkable_type: 'Issue', linkable_id: params[:id], linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id, linkable_type: 'Issue', be_linkable_id: params[:id], be_linkable_type: 'Issue'))
+ journal = @issue.journals.create!({user_id: current_user.id})
+ journal.journal_details.create!({property: "tag_link_issue", prop_key: "1", old_value: params[:id].to_s})
+ another_issue = Issue.find_by_id(params[:id])
+ if another_issue.present?
+ journal = another_issue.journals.create!({user_id: current_user.id})
+ journal.journal_details.create!({property: "tag_link_issue", prop_key: "1", old_value: @issue.id.to_s})
+ end
+ @link = @links.last
+ @link.destroy!
+ end
+ render_ok
+ rescue
+ render_error('删除失败!')
+ end
+ end
+end
diff --git a/app/controllers/api/pm/issue_tags_controller.rb b/app/controllers/api/pm/issue_tags_controller.rb
new file mode 100644
index 000000000..58feb6e8d
--- /dev/null
+++ b/app/controllers/api/pm/issue_tags_controller.rb
@@ -0,0 +1,71 @@
+class Api::Pm::IssueTagsController < Api::Pm::BaseController
+
+ def index
+ @issue_tags = IssueTag.pm_able
+ if params[:organization_id].present?
+ IssueTag.pm_org_init_data(params[:organization_id]) unless $redis_cache.hget("pm_org_init_issue_tags", params[:organization_id])
+ @issue_tags = @issue_tags.where(organization_id: params[:organization_id])
+ end
+ @issue_tags = @issue_tags.where(pm_project_id: params[:pm_project_id]) if params[:pm_project_id].present?
+ @issue_tags = @issue_tags.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
+ @issue_tags = @issue_tags.reorder("#{tag_sort_by} #{tag_sort_direction}")
+ @issue_tags = kaminari_paginate(@issue_tags)
+ render "api/v1/issues/issue_tags/index"
+ end
+
+ def create
+ return render_error("请输入正确的OrganizationID") unless Organization.exists?(id: issue_tag_create_params[:organization_id])
+ return render_error("项目标记名称不能为空!") unless issue_tag_create_params[:name].present?
+ @issue_tag = IssueTag.new(issue_tag_create_params.merge!(project_id: 0))
+ if @issue_tag.save!
+ render_ok
+ else
+ render_error("创建标记失败!")
+ end
+ end
+
+ before_action :load_issue_tag, only: [:update, :destroy]
+
+ def update
+ @issue_tag.attributes = issue_tag_update_params
+ if @issue_tag.save!
+ render_ok
+ else
+ render_error("更新标记失败!")
+ end
+ end
+
+ def destroy
+ if @issue_tag.destroy!
+ render_ok
+ else
+ render_error("删除标记失败!")
+ end
+ end
+
+
+ private
+ def tag_sort_by
+ sort_by = params.fetch(:sort_by, "created_at")
+ sort_by = IssueTag.column_names.include?(sort_by) ? sort_by : "created_at"
+ sort_by
+ end
+
+ def tag_sort_direction
+ sort_direction = params.fetch(:sort_direction, "desc")&.downcase
+ sort_direction = %w(desc asc).include?(sort_direction) ? sort_direction : "desc"
+ sort_direction
+ end
+
+ def issue_tag_create_params
+ params.permit(:name, :description, :color, :pm_project_id, :organization_id)
+ end
+
+ def issue_tag_update_params
+ params.permit(:name, :description, :color)
+ end
+
+ def load_issue_tag
+ @issue_tag = IssueTag.pm_able.find_by_id(params[:id])
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/api/pm/issues_controller.rb b/app/controllers/api/pm/issues_controller.rb
new file mode 100644
index 000000000..49cb5295c
--- /dev/null
+++ b/app/controllers/api/pm/issues_controller.rb
@@ -0,0 +1,368 @@
+class Api::Pm::IssuesController < Api::Pm::BaseController
+ before_action :require_login, except: [:index]
+ before_action :load_project
+ before_action :load_issue, only: %i[show update destroy link_index link_issues parent_issues]
+ before_action :load_issues, only: %i[batch_update batch_destroy]
+ before_action :check_issue_operate_permission, only: %i[update destroy]
+
+ def index
+ @object_result = Api::V1::Issues::ListService.call(@project, query_params, current_user)
+ @total_issues_count = @object_result[:total_issues_count]
+ @opened_issues_count = @object_result[:opened_issues_count]
+ @closed_issues_count = @object_result[:closed_issues_count]
+ @complete_issues_count = @object_result[:complete_issues_count]
+ if params[:only_name].present?
+ @issues = kaminary_select_paginate(
+ @object_result[:data].select(:id, :subject, :project_issues_index, :updated_on, :created_on))
+ else
+ @issues = kaminari_paginate(@object_result[:data])
+ end
+ render 'api/v1/issues/index'
+ end
+
+ def link_index
+ pm_issue_type = params[:pm_issue_type] || [1, 2, 3]
+ not_join_id = case params[:issue_filter_type]
+ when 'leaf_issue'
+ Issue.where(root_id: @issue.id).pluck(:id)
+ when 'link_issue'
+ @issue.pm_links.pluck(:be_linkable_id)
+ end
+
+ not_join_id << @issue.id
+ object_issues = Issue.where(
+ pm_project_id: params[:pm_project_id],
+ pm_issue_type: pm_issue_type
+ ).where.not(id: not_join_id).order(updated_on: :desc)
+
+ object_issues = object_issues.where(root_id: nil, child_count: 0) if params[:issue_filter_type] == 'leaf_issue'
+ @issues = kaminari_paginate(object_issues)
+ render 'api/v1/issues/index'
+ end
+
+ def parent_issues
+ @issues = Issue.where(pm_project_id: params[:pm_project_id])
+ .where.not(id: @issue.id)
+ .where.not(id: Issue.full_children_issues(@issue).map{|i|i.id})
+ @issues = @issues.where(pm_issue_type: params[:pm_issue_type]) if params[:pm_issue_type].present?
+ @issues = @issues.ransack(id_or_project_issues_index_eq: params[:keyword]).result.or(@issues.ransack(subject_or_description_cont: params[:keyword]).result) if params[:keyword].present?
+ @issues = @issues.reorder("#{issue_sort_by} #{issue_sort_direction}")
+ if params[:only_name].present?
+ @issues = kaminary_select_paginate(
+ @issues.select(:id, :subject, :project_issues_index, :updated_on, :created_on))
+ else
+ @issues = @issues.includes(:priority, :issue_status, :user, :show_assigners, :show_issue_tags, :version, :comment_journals)
+ @issues = kaminari_paginate(@issues)
+ end
+ end
+
+ def show
+ @issue.associate_attachment_container
+ render 'api/v1/issues/show'
+ end
+
+ def create
+ @object_result = Api::Pm::Issues::CreateService.call(@project, issue_params, current_user)
+ render 'api/v1/issues/create'
+ end
+
+ def update
+ @object_result = Api::Pm::Issues::UpdateService.call(@project, @issue, issue_params, current_user)
+ render 'api/v1/issues/update'
+ end
+
+ def batch_update
+ @object_result = Api::Pm::Issues::BatchUpdateService.call(@project, @issues, batch_issue_params, current_user)
+ if @object_result
+ render_ok
+ else
+ render_error('批量更新疑修失败!')
+ end
+ end
+
+ def batch_destroy
+ return render_ok if params[:ids].is_a?(Array) && params[:ids].blank?
+ @object_result = Api::Pm::Issues::BatchDeleteService.call(@project, @issues, current_user)
+ if @object_result
+ render_ok
+ else
+ render_error('批量删除疑修失败!')
+ end
+ end
+
+ def priorities
+ @priorities = IssuePriority.order(position: :asc)
+ @priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
+ @priorities = kaminary_select_paginate(@priorities)
+ render "api/v1/issues/issue_priorities/index"
+ end
+
+ def tags
+ # IssueTag.pm_init_data(params[:pm_project_id]) unless $redis_cache.hget("pm_project_init_issue_tags", params[:pm_project_id])
+ @issue_tags = IssueTag.where(pm_project_id: params[:pm_project_id]).reorder("#{tag_sort_by} #{tag_sort_direction}")
+ @issue_tags = @issue_tags.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
+ params[:only_name] = true #强制渲染 不走project
+ @issue_tags = kaminary_select_paginate(@issue_tags.select(:id, :name, :color))
+ render "api/v1/issues/issue_tags/index"
+ end
+
+ def statues
+ @statues = IssueStatus.order("position asc")
+ @statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
+ @statues = kaminary_select_paginate(@statues)
+ render "api/v1/issues/statues/index"
+ end
+
+
+
+ def destroy
+ @object_result = Api::Pm::Issues::DeleteService.call(@project, @issue, current_user)
+ if @object_result
+ render_ok
+ else
+ render_error('删除疑修失败!')
+ end
+ end
+
+ def export
+ return render_error('请输入正确的项目ID.') if params[:pm_project_id].blank?
+ Axlsx::Package.new do |p|
+ [['requirement', 1], ['task', 2], ['bug', 3]].each do |type|
+ p.workbook.add_worksheet(:name => type[0]) do |sheet|
+ @issues = Issue.where(pm_project_id: params[:pm_project_id], pm_issue_type: type[1])
+ sheet.add_row ["ID", "标题*", "正文", "创建者*", "创建时间", "修改者", "更新时间", "状态", "负责人", "优先级", "标记", "开始时间","结束时间", "预估工时"]
+ @issues.each do |issue|
+ sheet.add_row [issue.id, issue.subject, issue.description, issue.user.try(:login), issue.created_on.strftime("%Y-%m-%d %H:%M:%S"), issue.changer.try(:login), issue.updated_on.strftime("%Y-%m-%d %H:%M:%S"), issue.status_id, issue.assigners.pluck(:login).join(","), issue.priority_id, issue.issue_tags.pluck(:name, :color).join(","), issue.start_date.present? ? issue.start_date.strftime("%Y-%m-%d") : "", issue.due_date.present? ? issue.due_date.strftime("%Y-%m-%d") : "", issue.time_scale]
+ end
+ end
+ end
+ p.workbook.add_worksheet(:name => 'leaf_relations') do |sheet|
+ leaf_issues = Issue.where(pm_project_id: params[:pm_project_id]).where.not(root_id: nil)
+ sheet.add_row ["ID", "父工作项ID"]
+ leaf_issues.each do |issue|
+ sheet.add_row [issue.id, issue.root_id]
+ end
+ end
+ p.workbook.add_worksheet(:name => 'link_relations') do |sheet|
+ # links = PmLink.joins(:linkable_issue).where(issues: {pm_project_id: params[:pm_project_id]})
+ links = PmLink.find_by_sql("SELECT `pm_links`.* FROM `pm_links` INNER JOIN `issues` ON `issues`.`id` = `pm_links`.`linkable_id` AND `pm_links`.`linkable_type` = 'Issue' WHERE `issues`.`pm_project_id` = #{params[:pm_project_id]}")
+ sheet.add_row ["ID", "被关联工作项ID"]
+ links.each do |link|
+ sheet.add_row [link.linkable_id, link.be_linkable_id]
+ end
+ end
+ p.serialize('public/导出工作项.xlsx')
+ end
+
+ send_file('public/导出工作项.xlsx', :type => 'application/octet-stream;charset=utf-8')
+ end
+
+ def import
+ begin
+ return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile)
+ return render_error('请输入正确的项目ID.') if params[:pm_project_id].blank?
+ return render_error('请输入正确的组织ID.') if params[:organization_id].blank?
+ ActiveRecord::Base.transaction do
+ types = {requirement: 1, task: 2, bug: 3}
+ doc = SimpleXlsxReader.open(params[:file].tempfile)
+ doc.sheets.each do |sheet|
+ case sheet.name
+ when 'requirement', 'task', 'bug'
+
+ type = types["#{sheet.name}".to_sym]
+
+ sheet.rows.each.with_index do |row, index|
+ next if index == 0
+ issue = Issue.new(issue_classify: "issue", project_id: 0, pm_project_id: params[:pm_project_id], pm_issue_type: type, tracker_id: Tracker.first.id)
+ issue.fake_id = row[0]
+ issue.subject = row[1]
+ issue.description = row[2]
+ author = User.find_by(login: row[3])
+ issue.user = author
+ issue.created_on = row[4]
+ changer = User.find_by(login: row[5])
+ issue.changer = changer
+ issue.updated_on = row[6]
+ issue.status_id = row[7].to_i
+ if row[8].present?
+ row[8].split(',').each do |a|
+ u = User.find_by(login: a)
+ next unless u.present?
+ issue.assigners << u
+ end
+ end
+ issue.priority_id = row[9]
+ if row[10].present?
+ row[10].split(',').each_slice(2).to_a.each do |t|
+ tag = IssueTag.find_by(project_id: 0, organization_id: params[:organization_id], name: t[0])
+ if tag.present?
+ issue.issue_tags << tag
+ else
+ tag = IssueTag.create(project_id: 0,organization_id: params[:organization_id], name: t[0], color: t[1])
+ issue.issue_tags << tag
+ end
+ end
+ end
+ issue.start_date = row[11]
+ issue.due_date = row[12]
+ issue.time_scale = row[13]
+ issue.save!
+ end
+ when 'leaf_relations'
+ sheet.rows.each.with_index do |row, index|
+ next if index == 0
+ children_issue = Issue.where(fake_id: row[0]).last
+ parent_issue = Issue.where(fake_id: row[1]).last
+ next if children_issue.blank? || parent_issue.blank?
+ children_issue.root_id = parent_issue.id
+ children_issue.save(touch: false)
+ end
+ when 'link_relations'
+ sheet.rows.each.with_index do |row, index|
+ next if index == 0
+ link_issue = Issue.where(fake_id: row[0]).last
+ be_link_issue = Issue.where(fake_id: row[1]).last
+ next if link_issue.blank? || be_link_issue.blank?
+ PmLink.create!(linkable_type: 'Issue', linkable_id: link_issue.id, be_linkable_type: 'Issue', be_linkable_id: be_link_issue.id)
+ end
+ else
+ return render_error('导入失败,请上传正确格式的excel文件')
+ end
+
+ end
+ end
+ render_ok
+ rescue
+ return render_error('导入失败,请上传正确格式的excel文件')
+ end
+ end
+
+ def link_issues
+ children_issues = @issue.pm_issue_type == 1 ? @issue.child_count > 0 ? Issue.where(id: @issue.id) : Issue.none : Issue.where(root_id: @issue.id)
+ linkable_issues = Issue.where(id: PmLink.where(linkable_type: "Issue", linkable_id: @issue.id).pluck(:be_linkable_id))
+ belinkable_issues = Issue.where(id: PmLink.where(be_linkable_type: "Issue", be_linkable_id: @issue.id).pluck(:linkable_id))
+
+ full_link_issues_ids = children_issues.pluck(:id) | linkable_issues.pluck(:id) | belinkable_issues.pluck(:id)
+ compare_link_issues_ids = children_issues.pluck(:id) | linkable_issues.pluck(:id) | belinkable_issues.pluck(:id)
+ i = compare_link_issues_ids.count
+ while i > 0 do
+ children_issues = Issue.where(root_id: compare_link_issues_ids)
+ linkable_issues = Issue.where(id: PmLink.where(linkable_type: "Issue", linkable_id: compare_link_issues_ids).pluck(:be_linkable_id))
+ belinkable_issues = Issue.where(id: PmLink.where(be_linkable_type: "Issue", be_linkable_id: compare_link_issues_ids).pluck(:linkable_id))
+
+ compare_link_issues_ids = (children_issues.pluck(:id) | linkable_issues.pluck(:id) | belinkable_issues.pluck(:id)) - full_link_issues_ids
+ full_link_issues_ids = full_link_issues_ids | compare_link_issues_ids
+ i = compare_link_issues_ids.count
+ end
+ exclude_issues_ids = []
+ exclude_issues = Issue.where(id: full_link_issues_ids).where.not(root_id: nil)
+ exclude_issues.each do |i|
+ exclude_issues_ids << i.id if i.pm_issue_type == 1 && full_link_issues_ids.include?(i.root_id)
+ end
+ full_link_issues_ids = full_link_issues_ids - exclude_issues_ids
+ @requirement_issues = Issue.where(id:full_link_issues_ids, pm_issue_type:1, root_id: nil).pm_includes
+ @task_issues = Issue.where(id:full_link_issues_ids, pm_issue_type:2).pm_includes
+ @bug_issues = Issue.where(id:full_link_issues_ids, pm_issue_type:3).pm_includes
+ end
+
+ private
+ def circle_link_issues(issue_ids)
+ if issue_ids.present?
+ children_issues = Issue.joins(:parent_issue).where(issues: {id: issue_ids})
+ linkable_issues = Issue.where(id: PmLink.where(linkable_type: "Issue", linkable_id: issue_ids))
+ belinkable_issues = Issue.where(id: PmLink.where(be_linkable_type: "Issue", be_linkable_id: issue_ids))
+
+ return circle_link_issues(children_issues.pluck(:id))
+ else
+ return []
+ end
+ end
+
+ def check_issue_operate_permission
+ return if params[:project_id].to_i.zero?
+ render_forbidden('您没有操作权限!') unless @project.member?(current_user) || current_user.admin? || @issue.user == current_user
+ end
+
+ def load_issue
+ return render_parameter_missing if params[:pm_project_id].blank?
+ @issue = Issue.issue_issue.where(pm_project_id: params[:pm_project_id]).find_by_id(params[:id])
+ render_not_found('疑修不存在!') if @issue.blank?
+ end
+
+ def load_issues
+ return render_error('请输入正确的ID数组!') unless params[:ids].is_a?(Array)
+ params[:ids].each do |id|
+ @issue = Issue.find_by(id: id, pm_project_id: params[:pm_project_id])
+ return render_not_found("ID为#{id}的疑修不存在!") if @issue.blank?
+ end
+ if params[:ids].blank?
+ @issues = Issue.where(pm_project_id: params[:pm_project_id])
+ else
+ @issues = Issue.where(id: params[:ids], pm_project_id: params[:pm_project_id])
+ end
+ end
+
+
+ def query_params
+ params.permit(
+ :only_name,
+ :category,
+ :participant_category,
+ :keyword, :author_id,
+ :milestone_id, :assigner_id,
+ :status_id, :priority_id,
+ :begin_date, :end_date,
+ :update_begin_date, :update_end_date,
+ :sort_by, :sort_direction, :root_id,
+ :issue_tag_ids, :pm_project_id, :pm_sprint_id, :pm_issue_type, :pm_project_ids,
+ :status_ids, :ids, :exclude_ids, :pm_issue_types, :participator_id
+ )
+ end
+
+
+
+ def issue_params
+ params.permit(
+ :status_id, :priority_id, :milestone_id,
+ :branch_name, :start_date, :due_date, :time_scale,
+ :subject, :description, :blockchain_token_num, :root_subject,
+ :pm_project_id, :pm_sprint_id, :pm_issue_type, :root_id, :link_able_id, :project_id,
+ issue_tag_ids: [],
+ assigner_ids: [],
+ attachment_ids: [],
+ receivers_login: []
+ )
+ end
+
+ def batch_issue_params
+ params.permit(
+ :status_id, :priority_id, :milestone_id, :pm_sprint_id, :due_date, :pm_issue_type, :root_id, :target_pm_project_id, :project_id,
+ :issue_tag_ids => [],
+ :assigner_ids => [] )
+ end
+
+ def issue_sort_by
+ sort_by = params.fetch(:sort_by, "updated_on")
+ sort_by = Issue.column_names.include?(sort_by) ? sort_by : "updated_on"
+ sort_by
+ end
+
+ def issue_sort_direction
+ sort_direction = params.fetch(:sort_direction, "desc").downcase
+ sort_direction = %w(desc asc).include?(sort_direction) ? sort_direction : "desc"
+ sort_direction
+ end
+
+ def tag_sort_by
+ sort_by = params.fetch(:sort_by, "created_at")
+ sort_by = IssueTag.column_names.include?(sort_by) ? sort_by : "created_at"
+ sort_by
+ end
+
+ def tag_sort_direction
+ sort_direction = params.fetch(:sort_direction, "desc").downcase
+ sort_direction = %w(desc asc).include?(sort_direction) ? sort_direction : "desc"
+ sort_direction
+ end
+
+end
diff --git a/app/controllers/api/pm/journals_controller.rb b/app/controllers/api/pm/journals_controller.rb
new file mode 100644
index 000000000..b10cb4829
--- /dev/null
+++ b/app/controllers/api/pm/journals_controller.rb
@@ -0,0 +1,61 @@
+class Api::Pm::JournalsController < Api::Pm::BaseController
+ before_action :require_login, except: [:index, :children_journals]
+ before_action :load_project
+ before_action :load_issue
+ before_action :load_journal, only: [:children_journals, :update, :destroy]
+
+ def index
+ @object_result = Api::V1::Issues::Journals::ListService.call(@issue, query_params, current_user)
+ @total_journals_count = @object_result[:total_journals_count]
+ @total_operate_journals_count = @object_result[:total_operate_journals_count]
+ @total_comment_journals_count = @object_result[:total_comment_journals_count]
+ @journals = kaminary_select_paginate(@object_result[:data])
+ render 'api/pm/issues/journals/index'
+ end
+
+ def create
+ @object_result = Api::V1::Issues::Journals::CreateService.call(@issue, journal_params, current_user)
+ render 'api/v1/issues/journals/create'
+ end
+
+ def children_journals
+ @object_results = Api::V1::Issues::Journals::ChildrenListService.call(@issue, @journal, query_params, current_user)
+ @journals = kaminari_paginate(@object_results)
+ render 'api/v1/issues/journals/children_journals'
+ end
+
+ def update
+ @object_result = Api::V1::Issues::Journals::UpdateService.call(@issue, @journal, journal_params, current_user)
+ render 'api/v1/issues/journals/update'
+ end
+
+ def destroy
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueComment', @issue&.id, current_user.id, @journal.id, 'deleted', JSON.parse(@journal.to_builder.target!))
+ if @journal.destroy!
+ render_ok
+ else
+ render_error('删除评论失败!')
+ end
+ end
+
+ private
+
+ def query_params
+ params.permit(:category, :keyword, :sort_by, :sort_direction)
+ end
+
+ def journal_params
+ params.permit(:notes, :parent_id, :reply_id, :attachment_ids => [], :receivers_login => [])
+ end
+
+ def load_issue
+ @issue = Issue.issue_issue.where(pm_project_id: params[:pm_project_id]).find_by_id(params[:issue_id])
+ render_not_found('疑修不存在!') if @issue.blank?
+ end
+
+ def load_journal
+ @journal = Journal.find_by_id(params[:id])
+ render_not_found('评论不存在!') unless @journal.present?
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/pm/projects_controller.rb b/app/controllers/api/pm/projects_controller.rb
new file mode 100644
index 000000000..3cece6400
--- /dev/null
+++ b/app/controllers/api/pm/projects_controller.rb
@@ -0,0 +1,156 @@
+class Api::Pm::ProjectsController < Api::Pm::BaseController
+ before_action :require_login, except: [:convert]
+ before_action :load_project, only: [:convert]
+ def convert
+ data = {
+ owner: @project.owner.try(:login),
+ identifier: @project.identifier,
+ name: @project.name
+ }
+ render_ok(data: data)
+ end
+
+ def issues_count
+ return tip_exception '参数错误' unless params[:pm_project_id].present?
+ @issues = Issue.where(pm_project_id: params[:pm_project_id])
+ case params[:category].to_s
+ when 'closed'
+ @issues = @issues.closed
+ when 'opened'
+ @issues = @issues.opened
+ end
+ @participant_category_count = {}
+ if params[:participant_category].to_s == "authoredme" or params[:participant_category].to_s == "assignedme"
+ issues_category = @issues.joins(:issue_participants).where(pm_issue_type: [1, 2, 3]).where(issue_participants: {participant_type: %w[authored assigned atme], participant_id: current_user&.id})
+ @participant_category_count = issues_category.group(:pm_project_id, "issue_participants.participant_type").count
+ end
+ case params[:participant_category].to_s
+ when 'aboutme' # 关于我的
+ @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: %w[authored assigned atme], participant_id: current_user&.id})
+ when 'authoredme' # 我创建的
+ @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: 'authored', participant_id: current_user&.id})
+ when 'assignedme' # 我负责的
+ @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: 'assigned', participant_id: current_user&.id})
+ when 'atme' # @我的
+ @issues = @issues.joins(:issue_participants).where(issue_participants: {participant_type: 'atme', participant_id: current_user&.id})
+ end
+ data = {}
+ @issues_count = @issues.group(:pm_project_id).count
+ # requirement 1 task 2 bug 3
+ @issues_type_count = @issues.group(:pm_project_id, :pm_issue_type).count
+ params[:pm_project_id].map(&:to_i).map do |project_id|
+ data[project_id] = {
+ total: @issues_count[project_id] || 0,
+ requirement: @issues_type_count[[project_id, 1]] || 0,
+ task: @issues_type_count[[project_id, 2]] || 0,
+ bug: @issues_type_count[[project_id, 3]] || 0,
+ authoredme: @participant_category_count[[project_id, 0]] || 0,
+ assignedme: @participant_category_count[[project_id, 1]] || 0,
+ atme: @participant_category_count[[project_id, 4]] || 0,
+ }
+ end
+ render_ok(data: data)
+ end
+
+
+ def statistics
+ return tip_exception '参数错误' if params[:pm_project_id].blank?
+ @issues = Issue.where(pm_project_id: params[:pm_project_id], pm_issue_type:[1, 2, 3])
+ @last_week_close_issues = @issues.where(status_id: 5).where("updated_on > ? and updated_on < ?", Time.now - 7.days, Time.now)
+ last_week_close_type_count_data = @last_week_close_issues.group(:pm_issue_type).count
+ type_count_data = @issues.group(:pm_issue_type).count
+ type_status = @issues.group(:pm_issue_type,:status_id).count
+ type_status_data = {}
+ IssueStatus.all.map do |e|
+ # next if e.id == 5
+ [1,2,3].map{ |type|
+ next if type == 1 && [1, 6].include?(e.id)
+ type_status_data[type] = {} if type_status_data[type].nil?
+ if type_status[[type,e.id]].nil?
+ type_status_data[type][e.id] = 0
+ else
+ type_status_data[type][e.id] = type_status[[type,e.id]]
+ end
+ }
+ end
+ open_data = {
+ "1": type_status_data[1][1].to_i + type_status_data[1][2].to_i + type_status_data[1][3].to_i + type_status_data[1][6].to_i,
+ "2": type_status_data[2][1].to_i + type_status_data[2][2].to_i + type_status_data[2][3].to_i + type_status_data[2][6].to_i,
+ "3": type_status_data[3][1].to_i + type_status_data[3][2].to_i + type_status_data[3][3].to_i + type_status_data[3][6].to_i,
+ }
+ if type_count_data.keys.size < 3
+ nedd_add = [1,2,3] - type_count_data.keys
+ nedd_add.map{ |e|
+ type_count_data[e] = 0
+ }
+ end
+ if last_week_close_type_count_data.keys.size < 3
+ nedd_add = [1,2,3] - last_week_close_type_count_data.keys
+ nedd_add.map{ |e|
+ last_week_close_type_count_data[e] = 0
+ }
+ end
+ data = {
+ pie_chart: type_count_data,
+ bar_chart: type_status_data,
+ open_data: open_data,
+ last_week_close_data: last_week_close_type_count_data,
+ }
+ render_ok(data: data)
+ end
+
+ def polyline
+ return tip_exception '参数错误' if params[:pm_project_id].blank?
+ time_line = (Time.current.beginning_of_day - 29.day) .. Time.current
+ @create_issues = Issue.where(pm_project_id: params[:pm_project_id],created_on: time_line)
+ @due_issues = Issue.where(pm_project_id: params[:pm_project_id],status_id:[3,5],due_date: time_line)
+ @create_issues_count = @create_issues.group(:pm_issue_type,"DATE(created_on)").count
+ @due_issues_count = @due_issues.group(:pm_issue_type,"DATE(due_date)").count
+ data = {
+ create_issues: {},
+ due_issues: {}
+ }
+ 30.times do |time|
+ current_time = Date.current - time.day
+ if @create_issues_count.present?
+ data[:create_issues][current_time] = {
+ "1": @create_issues_count[[1,current_time]] || 0,
+ "2": @create_issues_count[[2,current_time]] || 0,
+ "3": @create_issues_count[[3,current_time]] || 0
+ }
+ else
+ data[:create_issues][current_time] = {
+ "1": 0,
+ "2": 0,
+ "3": 0
+ }
+ end
+ if @due_issues_count.present?
+ data[:due_issues][current_time] = {
+ "1": @due_issues_count[[1,current_time]] || 0,
+ "2": @due_issues_count[[2,current_time]] || 0,
+ "3": @due_issues_count[[3,current_time]] || 0
+ }
+ else
+ data[:due_issues][current_time] = {
+ "1": 0,
+ "2": 0,
+ "3": 0
+ }
+ end
+ end
+ render_ok(data: data)
+ end
+
+ def bind_project
+ return render_forbidden('您没有操作权限!') unless @project.member?(current_user) || current_user.admin?
+ Issue.where(pm_project_id: params[:pm_project_id], user_id: current_user).update_all(project_id: params[:project_id])
+ end
+
+ private
+ def load_project
+ @project = Project.joins(:owner).find params[:project_id]
+ end
+
+
+end
diff --git a/app/controllers/api/pm/sprint_issues_controller.rb b/app/controllers/api/pm/sprint_issues_controller.rb
new file mode 100644
index 000000000..732ff524f
--- /dev/null
+++ b/app/controllers/api/pm/sprint_issues_controller.rb
@@ -0,0 +1,109 @@
+class Api::Pm::SprintIssuesController < Api::Pm::BaseController
+
+ before_action :require_login, except: [:index]
+
+ def index
+ @issues = Api::Pm::SprintIssues::ListService.call(query_params, current_user)
+ @issues = kaminari_paginate(@issues)
+ render 'api/v1/issues/index'
+ end
+
+ def burndown_charts
+ return tip_exception '参数错误' if params[:pm_sprint_id].blank? || params[:start_time].blank? || params[:end_time].blank?
+ @issues = Issue.where(pm_sprint_id: params[:pm_sprint_id])
+ start_time = Date.parse params[:start_time]
+ end_time = Date.parse params[:end_time]
+ time_count = (end_time - start_time).to_i + 1 # 计算间隔时间 加上最后一天
+ data = []
+ curren_issues = @issues.group(:status_id, :due_date).count
+ total_count = @issues.count
+ cardinality = (total_count.zero? || time_count.zero?) ? 0 : total_count.to_f / time_count.to_f
+ # cardinality = BigDecimal.new(total_count) / BigDecimal.new(time_count)
+ time_count.times do |x|
+ e_time = start_time + x
+ completed = curren_issues[[5, e_time]].to_i + curren_issues[[3, e_time]].to_i - @issues.where(pm_issue_type: 3, status_id: 3).size
+ total_count = total_count - completed
+ data << { time: e_time, undone: total_count, completed: completed, base_number: (cardinality * (time_count - x - 1)).to_f.round(2) }
+ end
+ render_ok(data: data)
+ end
+
+ def statistics
+ pm_sprint_ids = params[:pm_sprint_ids].split(",") rescue []
+ return tip_exception '参数错误' if pm_sprint_ids.blank?
+ @issues = Issue.where(pm_sprint_id: pm_sprint_ids)
+ data = {}
+ # requirement 1 task 2 bug 3
+ @issues_count = @issues.group(:pm_sprint_id).count
+ @issues_type_count = @issues.group(:pm_sprint_id, :status_id).count
+ @issues_pm_type_count = @issues.group(:pm_sprint_id, :pm_issue_type).count
+ @issues_hour_count = @issues.group(:pm_sprint_id).sum(:time_scale)
+ @issues_hour_type_count = @issues.group(:pm_sprint_id, :status_id).sum(:time_scale)
+ @issues_hour_pm_type_count = @issues.group(:pm_sprint_id, :pm_issue_type).sum(:time_scale)
+ @issues_status_pm_type_count = @issues.group(:pm_sprint_id, :pm_issue_type, :status_id).count
+ pm_sprint_ids.map(&:to_i).map do |sprint_id|
+ # count_closed 工作项已完成/已关闭数量,需排除已修复的缺陷数量
+ count_closed = @issues_type_count[[sprint_id, 5]].to_i + @issues_type_count[[sprint_id, 3]].to_i - @issues.where(pm_sprint_id: sprint_id, pm_issue_type: 3, status_id: 3).size
+ # hour_closed 已完成/已关闭 预估工时之和,需排除已修复的缺陷预估工时
+ hour_closed = @issues_hour_type_count[[sprint_id, 5]].to_f + @issues_hour_type_count[[sprint_id, 3]].to_f - @issues.where(pm_sprint_id: sprint_id, pm_issue_type: 3, status_id: 3).sum(:time_scale).to_f
+ data[sprint_id] = {
+ count_total: @issues_count[sprint_id] || 0,
+ count_closed: count_closed || 0,
+ hour_total: @issues_hour_count[sprint_id].to_f || 0,
+ hour_closed: hour_closed || 0,
+ requirement: @issues_pm_type_count[[sprint_id, 1]] || 0,
+ task: @issues_pm_type_count[[sprint_id, 2]] || 0,
+ bug: @issues_pm_type_count[[sprint_id, 3]] || 0,
+ requirement_hour: @issues_hour_pm_type_count[[sprint_id, 1]].to_i || 0,
+ task_hour: @issues_hour_pm_type_count[[sprint_id, 2]].to_i || 0,
+ bug_hour: @issues_hour_pm_type_count[[sprint_id, 3]].to_i || 0,
+ requirement_open: (@issues_status_pm_type_count[[sprint_id, 1, 1]].to_i + @issues_status_pm_type_count[[sprint_id, 1, 2]].to_i) || 0,
+ task_open: @issues_status_pm_type_count[[sprint_id, 2, 1]].to_i + @issues_status_pm_type_count[[sprint_id, 2, 2]].to_i || 0,
+ bug_open: @issues_status_pm_type_count[[sprint_id, 3, 1]].to_i + @issues_status_pm_type_count[[sprint_id, 3, 2]].to_i || 0
+ }
+ end
+ render_ok(data: data)
+ end
+
+ before_action :load_uncomplete_issues, only: [:complete]
+
+ def complete
+ begin
+ case complete_params[:complete_type].to_i
+ when 1
+ @issues.update_all(status_id: 5)
+ when 2
+ @issues.update_all(pm_sprint_id: 0)
+ when 3
+ @issues.update_all(pm_sprint_id: complete_params[:target_pm_project_sprint_id])
+ end
+ render_ok
+ rescue => e
+ render_error(e.message)
+ end
+ end
+
+ private
+
+ def load_uncomplete_issues
+ @issues = Issue.where(pm_sprint_id: complete_params[:pm_project_sprint_id]).where.not(status_id: 5)
+ end
+
+ def complete_params
+ params.permit(:pm_project_sprint_id, :complete_type, :target_pm_project_sprint_id)
+ end
+
+ def query_params
+ params.permit(
+ :category,
+ :pm_project_id,
+ :pm_issue_type, # 需求1 任务2 缺陷3
+ :assigner_id,
+ :priority_id,
+ :status_id,
+ :keyword, :status_ids, :pm_issue_types,
+ :sort_by, :sort_direction
+ )
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb
index ea2266390..d9db4b99c 100644
--- a/app/controllers/api/v1/base_controller.rb
+++ b/app/controllers/api/v1/base_controller.rb
@@ -62,7 +62,7 @@ class Api::V1::BaseController < ApplicationController
# 具有对仓库的访问权限
def require_public_and_member_above
- @project = load_project
+ @project = load_project
return render_forbidden if !@project.is_public && !current_user.admin? && !@project.member?(current_user)
end
end
\ No newline at end of file
diff --git a/app/controllers/api/v1/issues/issue_priorities_controller.rb b/app/controllers/api/v1/issues/issue_priorities_controller.rb
index 2df1288f7..319994a28 100644
--- a/app/controllers/api/v1/issues/issue_priorities_controller.rb
+++ b/app/controllers/api/v1/issues/issue_priorities_controller.rb
@@ -7,12 +7,4 @@ class Api::V1::Issues::IssuePrioritiesController < Api::V1::BaseController
@priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
@priorities = kaminary_select_paginate(@priorities)
end
-
- def pm_index
- @priorities = IssuePriority.order(position: :asc)
- @priorities = @priorities.ransack(name_cont: params[:keyword]).result if params[:keyword]
- @priorities = kaminary_select_paginate(@priorities)
- render "index"
- end
-
end
\ No newline at end of file
diff --git a/app/controllers/api/v1/issues/issue_tags_controller.rb b/app/controllers/api/v1/issues/issue_tags_controller.rb
index 39534c313..f712a3ba4 100644
--- a/app/controllers/api/v1/issues/issue_tags_controller.rb
+++ b/app/controllers/api/v1/issues/issue_tags_controller.rb
@@ -13,12 +13,7 @@ class Api::V1::Issues::IssueTagsController < Api::V1::BaseController
end
end
- def pm_index
- @issue_tags = IssueTag.init_mp_issues_tags
- render_ok(@issue_tags)
- end
-
- def create
+ def create
@issue_tag = @project.issue_tags.new(issue_tag_params)
if @issue_tag.save!
render_ok
diff --git a/app/controllers/api/v1/issues/journals_controller.rb b/app/controllers/api/v1/issues/journals_controller.rb
index 4a88c1b0f..a549714ff 100644
--- a/app/controllers/api/v1/issues/journals_controller.rb
+++ b/app/controllers/api/v1/issues/journals_controller.rb
@@ -46,7 +46,7 @@ class Api::V1::Issues::JournalsController < Api::V1::BaseController
end
def load_issue
- @issue = @project.issues.issue_issue.where(project_issues_index: params[:index]).where.not(id: params[:index]).take || Issue.find_by_id(params[:index])
+ @issue = @project.issues.issue_issue.where(project_issues_index: params[:index]).where.not(id: params[:index]).take || @project.issues.issue_issue.find_by_id(params[:index])
if @issue.blank?
render_not_found("疑修不存在!")
end
diff --git a/app/controllers/api/v1/issues/statues_controller.rb b/app/controllers/api/v1/issues/statues_controller.rb
index c6495ee26..5a7fbc338 100644
--- a/app/controllers/api/v1/issues/statues_controller.rb
+++ b/app/controllers/api/v1/issues/statues_controller.rb
@@ -8,11 +8,4 @@ class Api::V1::Issues::StatuesController < Api::V1::BaseController
@statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
@statues = kaminary_select_paginate(@statues)
end
-
- def pm_index
- @statues = IssueStatus.order("position asc")
- @statues = @statues.ransack(name_cont: params[:keyword]).result if params[:keyword].present?
- @statues = kaminary_select_paginate(@statues)
- render "index"
- end
end
\ No newline at end of file
diff --git a/app/controllers/api/v1/issues_controller.rb b/app/controllers/api/v1/issues_controller.rb
index 1737b7c97..e56f15cba 100644
--- a/app/controllers/api/v1/issues_controller.rb
+++ b/app/controllers/api/v1/issues_controller.rb
@@ -70,7 +70,7 @@ class Api::V1::IssuesController < Api::V1::BaseController
private
def load_issue
- @issue = @project.issues.issue_issue.where(project_issues_index: params[:index]).where.not(id: params[:index]).take || Issue.find_by_id(params[:index])
+ @issue = @project.issues.issue_issue.where(project_issues_index: params[:index]).where.not(id: params[:index]).take || @project.issues.issue_issue.find_by_id(params[:index])
if @issue.blank?
render_not_found("疑修不存在!")
end
diff --git a/app/controllers/api/v1/pm_issues_controller.rb b/app/controllers/api/v1/pm_issues_controller.rb
new file mode 100644
index 000000000..5c2e0f987
--- /dev/null
+++ b/app/controllers/api/v1/pm_issues_controller.rb
@@ -0,0 +1,36 @@
+class Api::V1::PmIssuesController < ApplicationController
+ before_action :require_login, except: [:index, :show]
+
+ def index
+ project = Project.find_by_id(params[:project_id]) || Project.new( id: 0, user_id: 0, name:"pm_mm", identifier:"pm_mm" )
+ object_result = Api::V1::Issues::ListService.call(@project, query_params, current_user)
+ @total_issues_count = @object_result[:total_issues_count]
+ @opened_issues_count = @object_result[:opened_issues_count]
+ @closed_issues_count = @object_result[:closed_issues_count]
+ if params[:only_name].present?
+ @issues = kaminary_select_paginate(@object_result[:data].select(:id, :subject, :project_issues_index, :updated_on, :created_on))
+ else
+ @issues = kaminari_paginate(@object_result[:data])
+ end
+ end
+
+ def create
+ project = Project.find_by_id(params[:project_id]) || Project.new( id: 0, user_id: 0, name:"pm_mm", identifier:"pm_mm" )
+ @object_result = Api::V1::Issues::CreateService.call(project, issue_params, current_user)
+ end
+
+ private
+ def issue_params
+ params.permit(
+ :status_id, :priority_id, :milestone_id,
+ :branch_name, :start_date, :due_date,
+ :subject, :description, :blockchain_token_num,
+ :pm_project_id, :pm_sprint_id,
+ :issue_tag_ids => [],
+ :assigner_ids => [],
+ :attachment_ids => [],
+ :receivers_login => []
+ )
+ end
+
+end
diff --git a/app/controllers/api/v1/projects/actions/actions_controller.rb b/app/controllers/api/v1/projects/actions/actions_controller.rb
index 3825b4685..cc80b68de 100644
--- a/app/controllers/api/v1/projects/actions/actions_controller.rb
+++ b/app/controllers/api/v1/projects/actions/actions_controller.rb
@@ -1,5 +1,63 @@
class Api::V1::Projects::Actions::ActionsController < Api::V1::Projects::Actions::BaseController
+ def new_index
+ @files = $gitea_client.get_repos_contents_by_owner_repo_filepath(@project&.owner&.login, @project&.identifier, ".gitea/workflows") rescue []
+ @workflows = params[:workflows].split(",") if params[:workflows].present?
+ @action_runs = Gitea::ActionRun.where(repo_id: @project.gpid)
+ @action_runs = @action_runs.where(id: params[:ids].split(",")) if params[:ids].present?
+ @action_runs = @action_runs.where(workflow_id: @workflows) if params[:workflows].present?
+ group_data = @action_runs.where(status: [1,2]).group(:workflow_id, :status).count
+ @result = []
+ @files.map{|i|i['name']}.each do |file|
+ if @workflows.present?
+ next if !@workflows.include?(file)
+ end
+ last_action_run = @action_runs.where(workflow_id: file).order(updated: :desc).first
+ last_action_run_json = last_action_run.present? ? {
+ id: last_action_run.id,
+ schedule: last_action_run.schedule_id > 0,
+ title: last_action_run.title,
+ index: last_action_run.index,
+ status: last_action_run.status,
+ started: last_action_run.started,
+ stopped: last_action_run.stopped,
+ length: last_action_run.stopped-last_action_run.started,
+ created: last_action_run.created,
+ updated: last_action_run.updated,
+ } : {}
+
+ total = 0
+ success = 0
+ failure = 0
+ group_data.each do |k,v|
+ total += v if k[0] == file
+ success += v if k[0] == file && k[1] == 1
+ failure += v if k[0] == file && k[1] == 2
+ end
+
+ pipeline_type = 1
+ begin
+ content = Gitea::Repository::Entries::GetService.call(@project&.owner, @project&.identifier, URI.escape(file), ref: last_action_run.present? ? last_action_run.ref.gsub("refs/heads/","") : @project.default_branch)['content']
+ yaml_string = Base64.decode64(content).force_encoding("GBK").encode("UTF-8") unless Base64.decode64(content).force_encoding('UTF-8').valid_encoding?
+ yaml_string = Base64.decode64(content).force_encoding('UTF-8')
+ yml = YAML.safe_load(yaml_string)
+ pipeline_type = yml.name == file.to_s.gsub(".yml","").gsub(".yaml","") ? 2 : 1
+ rescue
+ Rails.logger.info("#{file}不能识别流水线类型")
+ end
+ @result << {
+ filename: file,
+ name: file.to_s.gsub(".yml","").gsub(".yaml","") ,
+ branch: last_action_run.present? ? last_action_run.ref.gsub("refs/heads/","") : @project.default_branch,
+ pipeline_type: pipeline_type,
+ total: total,
+ success: success,
+ failure: failure
+ }.merge(last_action_run_json)
+ end
+ render :json => {data: @result}
+ end
+
def index
begin
gitea_result = $gitea_hat_client.get_repos_actions_by_owner_repo(@project&.owner&.login, @project&.identifier)
diff --git a/app/controllers/api/v1/projects/actions/runs_controller.rb b/app/controllers/api/v1/projects/actions/runs_controller.rb
index 9d0cdb8b0..cdde508a1 100644
--- a/app/controllers/api/v1/projects/actions/runs_controller.rb
+++ b/app/controllers/api/v1/projects/actions/runs_controller.rb
@@ -1,8 +1,9 @@
class Api::V1::Projects::Actions::RunsController < Api::V1::Projects::Actions::BaseController
- def index
+ def index
@result_object = Api::V1::Projects::Actions::Runs::ListService.call(@project, {workflow: params[:workflow], page: page, limit: limit}, current_user&.gitea_token)
- puts @result_object
+ @begin_num = (page.to_i - 1) * limit.to_i
+ # puts @result_object
end
def create
diff --git a/app/controllers/api/v1/projects/commits_controller.rb b/app/controllers/api/v1/projects/commits_controller.rb
index 98fdc290f..b80d09750 100644
--- a/app/controllers/api/v1/projects/commits_controller.rb
+++ b/app/controllers/api/v1/projects/commits_controller.rb
@@ -1,5 +1,5 @@
class Api::V1::Projects::CommitsController < Api::V1::BaseController
- before_action :require_public_and_member_above, only: [:index, :diff, :recent]
+ before_action :require_public_and_member_above, only: [:index, :diff, :recent, :files]
def index
@result_object = Api::V1::Projects::Commits::ListService.call(@project, {page: page, limit: limit, sha: params[:sha]}, current_user&.gitea_token)
@@ -10,6 +10,14 @@ class Api::V1::Projects::CommitsController < Api::V1::BaseController
@result_object = Api::V1::Projects::Commits::DiffService.call(@project, params[:sha], current_user&.gitea_token)
end
+ def files
+ if params[:filepath].present?
+ @result_object = $gitea_hat_client.get_repos_commits_files_by_owner_repo_sha_filepath(@project&.owner.login, @project&.identifier, params[:sha], CGI.escape(params[:filepath]), {query: {token: current_user&.gitea_token}})
+ else
+ @result_object = $gitea_hat_client.get_repos_commits_files_by_owner_repo_sha(@project&.owner.login, @project&.identifier, params[:sha], {query: {token: current_user&.gitea_token, page: page, limit: limit}})
+ end
+ end
+
def recent
hash = Api::V1::Projects::Commits::RecentService.call(@project, {keyword: params[:keyword], page: page, limit: limit}, current_user&.gitea_token)
@result_object = hash[:result]
diff --git a/app/controllers/api/v1/projects/compare_controller.rb b/app/controllers/api/v1/projects/compare_controller.rb
new file mode 100644
index 000000000..8a40ba519
--- /dev/null
+++ b/app/controllers/api/v1/projects/compare_controller.rb
@@ -0,0 +1,58 @@
+class Api::V1::Projects::CompareController < Api::V1::BaseController
+
+ before_action :require_public_and_member_above, only: [:files]
+
+ def files
+ load_compare_params
+ if params[:type] == "sha"
+ @compare_result ||= gitea_compare_files(@base, @head)
+ else
+ @compare_result ||= @head.include?(":") ? gitea_compare_files(@base, @head) : gitea_compare_files(@head, @base)
+ @merge_status, @merge_message = get_merge_message
+ end
+ end
+
+ private
+ def load_compare_params
+ @base = params[:base].include?(":") ? Addressable::URI.unescape(params[:base].split(":")[0]) + ':' + Base64.decode64(params[:base].split(":")[1]) : Base64.decode64(params[:base])
+ @head = params[:head].include?(":") ? Addressable::URI.unescape(params[:head].split(":")[0]) + ':' + Base64.decode64(params[:head].split(":")[1]) : Base64.decode64(params[:head])
+ end
+
+ def gitea_compare_files(base, head)
+ if params[:filepath].present?
+ $gitea_hat_client.get_repos_compare_by_owner_repo_baseref_headref(@project&.owner&.login, @project.identifier, Addressable::URI.escape(base), Addressable::URI.escape(head), {query: {token: current_user&.gitea_token, isFiles: true, filepath: params[:filepath]}})
+ else
+ $gitea_hat_client.get_repos_compare_by_owner_repo_baseref_headref(@project&.owner&.login, @project.identifier, Addressable::URI.escape(base), Addressable::URI.escape(head), {query: {page:page,limit:limit,token: current_user&.gitea_token, isFiles: true}})
+ end
+ end
+
+ def get_merge_message
+ if @base.blank? || @head.blank?
+ return -2, "请选择分支"
+ else
+ return -2, "目标仓库未开启合并请求(PR)功能" unless @project.has_menu_permission("pulls")
+ if @head.include?(":")
+ fork_project = @project.forked_projects.joins(:owner).where(users: {login: @head.to_s.split("/")[0]}).take
+ return -2, "请选择正确的仓库" unless fork_project.present?
+ @exist_pullrequest = @project.pull_requests.where(is_original: true, head: @head.to_s.split(":")[1], base: @base, status: 0, fork_project_id: fork_project.id).take
+ else
+ @exist_pullrequest = @project.pull_requests.where(is_original: false, head: @base, base: @head, status: 0).take
+ end
+ if @exist_pullrequest.present?
+ return -2, "在这些分支之间的合并请求已存在:#{@exist_pullrequest.try(:title)}"
+ else
+ Rails.logger.info @compare_result
+ if params[:filepath].present?
+ if @compare_result["Commits"].blank? && @compare_result["Diff"].blank?
+ return -2, "分支内容相同,无需创建合并请求"
+ end
+ else
+ if @compare_result[:total_data].to_i < 1
+ return -2, "分支内容相同,无需创建合并请求"
+ end
+ end
+ end
+ end
+ return 0, "可以合并"
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/oss_health_measuring_controller.rb b/app/controllers/api/v1/projects/oss_health_measuring_controller.rb
new file mode 100644
index 000000000..5cc95907f
--- /dev/null
+++ b/app/controllers/api/v1/projects/oss_health_measuring_controller.rb
@@ -0,0 +1,31 @@
+class Api::V1::Projects::OssHealthMeasuringController < Api::V1::BaseController
+
+ def index
+
+ url = URI("#{EduSetting.get("ohm_server_url")}/api/OSS_Health_Measuring/#{params[:owner]}/#{params[:repo]}")
+
+ http = Net::HTTP.new(url.host, url.port);
+ request = Net::HTTP::Get.new(url)
+ response = http.request(request)
+ render :json=> response.read_body
+ end
+
+ def keyid
+ url = URI("#{EduSetting.get("ohm_server_url")}/api/OSS_Health_Measuring/#{params[:owner]}/#{params[:repo]}/#{params[:key_id]}")
+
+ http = Net::HTTP.new(url.host, url.port);
+ request = Net::HTTP::Get.new(url)
+ response = http.request(request)
+ render :json=> response.read_body
+ end
+
+ def can_get
+ url = URI("#{EduSetting.get("ohm_server_url")}/api/OHM_can_get/#{params[:owner]}/#{params[:repo]}")
+
+ http = Net::HTTP.new(url.host, url.port);
+ request = Net::HTTP::Get.new(url)
+ response = http.request(request)
+ render :json=> response.read_body
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/api/v1/projects/pipelines_controller.rb b/app/controllers/api/v1/projects/pipelines_controller.rb
new file mode 100644
index 000000000..68305a40b
--- /dev/null
+++ b/app/controllers/api/v1/projects/pipelines_controller.rb
@@ -0,0 +1,2068 @@
+class Api::V1::Projects::PipelinesController < Api::V1::BaseController
+ before_action :require_operate_above, except: [:upload_results, :run_results]
+
+ def index
+ @pipelines = Action::Pipeline.where(project_id: @project.id).order(updated_at: :desc)
+ @pipelines = paginate @pipelines
+ end
+
+ def test_yaml
+ pipeline_yaml = build_pipeline_yaml_new("test", JSON.parse(demo2.to_json))
+ respond_to do |format|
+ format.html { @nodes = Action::Node.all }
+ # format.yaml { @nodes = Action::Node.all }
+ format.yaml { render template: "api/v1/projects/pipelines/test_yaml", content_type: "text/html" }
+ format.json { render_ok({ pipeline_yaml: pipeline_yaml }) }
+ end
+ end
+
+ def create
+ size = Action::Pipeline.where(pipeline_name: params[:pipeline_name], project_id: @project.id).size
+ tip_exception("已经存在#{params[:pipeline_name]}流水线!") if size > 0
+ @pipeline = Action::Pipeline.new(pipeline_name: params[:pipeline_name], project_id: @project.id)
+ @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
+ @pipeline.branch = params[:branch] || @project.default_branch
+ @pipeline.json = params[:pipeline_json].to_json
+ pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
+ tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
+ @pipeline.yaml = pipeline_yaml
+ @pipeline.save!
+ sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
+ tip_exception("#{@pipeline.file_name}已存在") if sha
+ interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create"))
+ tip_exception(interactor.error) unless interactor.success?
+ render_ok({ id: @pipeline.id })
+ end
+
+ def save_yaml
+ @pipeline = Action::Pipeline.new(pipeline_name: params[:pipeline_name], project_id: @project.id)
+ @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
+ @pipeline.branch = params[:branch] || @project.default_branch
+ @pipeline.json = params[:pipeline_json].to_json
+ pipeline_yaml = build_pipeline_yaml_new(params[:pipeline_name], params[:pipeline_json])
+ tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
+ @pipeline.yaml = pipeline_yaml
+ Rails.logger.info "pipeline_yaml base64=========================#{Base64.encode64(@pipeline.yaml).gsub(/\n/, '')}"
+ sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
+ interactor = sha.present? ? Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update").merge(sha: sha)) : Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create"))
+ tip_exception(interactor.error) unless interactor.success?
+ file = interactor.result
+ render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: params[:pipeline_name], file_name: @pipeline.file_name, sha: sha.present? ? sha : file['content']['sha'] })
+ end
+
+ def build_yaml
+ if params[:pipeline_json].present?
+ pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
+ else
+ pipeline_yaml = build_test_yaml
+ end
+ # render plain: pipeline_yaml
+ render_ok({ pipeline_yaml: pipeline_yaml })
+ end
+
+ def update
+ @pipeline = Action::Pipeline.find(params[:id])
+ @pipeline.pipeline_name = params[:pipeline_name]
+ @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml"
+ @pipeline.branch = params[:branch] || @project.default_branch
+ @pipeline.json = params[:pipeline_json].to_json
+ pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json])
+ tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank?
+ @pipeline.yaml = pipeline_yaml
+ @pipeline.save
+ sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch)
+ interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create").merge(sha: sha))
+ tip_exception(interactor.error) unless interactor.success?
+ file = interactor.result
+ render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: params[:pipeline_name], file_name: @pipeline.file_name, sha: file['content']['sha'] })
+ end
+
+ def destroy
+ @pipeline = Action::Pipeline.find(params[:id])
+ if pipeline
+ interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update"))
+ tip_exception(interactor.error) unless interactor.success?
+ @pipeline.destroy!
+ end
+ render_ok
+ end
+
+
+ def upload_results
+ tip_exception("参数错误") if params[:owner].blank? || params[:repo].blank? || params[:run_id].blank?
+ @project, @owner = Project.find_with_namespace(params[:owner], params[:repo])
+ tip_exception("项目不存在") if @project.blank?
+ result = Action::PipelineResult.find_or_initialize_by(run_id: params[:run_id], step_id: params[:step_id], project_id: @project.id)
+ result.step_id = params[:step_id]
+ result.job_name = params[:job_name]
+ result.job_show_type = params[:job_show_type] || "html"
+ result.job_result = params[:job_result]
+ if result.save!
+ render_ok
+ else
+ render_error("保存失败")
+ end
+ end
+
+ def run_results
+ tip_exception("参数错误") if params[:owner].blank? || params[:repo].blank? || params[:run_id].blank?
+ @project, @owner = Project.find_with_namespace(params[:owner], params[:repo])
+ tip_exception("项目不存在") if @project.blank?
+ results = Action::PipelineResult.where(run_id: params[:run_id], project_id: @project.id)
+ render_ok(run_results: results.as_json(only: %i[id run_id job_name job_show_type job_result]))
+ end
+
+ def show
+ @pipeline = Action::Pipeline.find_by(id: params[:id])
+ @pipeline = Action::Pipeline.new(id: 0, pipeline_name: "test-ss", yaml: build_test_yaml) if @pipeline.blank?
+ end
+
+ def build_pipeline_yaml_new(pipeline_name, pipeline_json)
+ @pipeline_name = pipeline_name
+ job_nodes = pipeline_json["nodes"].select { |node| node["data"]["name"].to_s.include?("job") }
+ on_nodes = pipeline_json["nodes"].select { |node| ["on-push", "on-schedule", "on-pull_request", "on-fork"].include?(node["data"]["name"]) }
+ @on_nodes = build_nodes(on_nodes)
+ @job_nodes = []
+ job_nodes.each do |job|
+ node = Action::Node.find_by(name: job["data"]["name"])
+ next if node.blank?
+ node.label = job["data"]["label"] if job["data"]["label"].present?
+ node.node_id = job["id"] if job["id"].present?
+ next_edge_nodes = pipeline_json["edges"].select { |edge| edge["source"]["cell"].include?(job["id"]) }
+ node_ids = next_edge_nodes.map { |t| t["target"]["cell"] }
+ next_step_node = pipeline_json["nodes"].select { |node| node_ids.include?(node["id"]) && !node["data"]["name"].to_s.include?("job") }&.first
+ parent_node = pipeline_json["edges"].select { |edge| edge["target"]["cell"].include?(job["id"]) }.first
+ node.parent_node_id = parent_node.present? ? parent_node["source"]["cell"] : ""
+ node.sub_nodes = []
+ get_all_child_nodes(pipeline_json, node, next_step_node) if next_step_node.present?
+ @job_nodes.push(node)
+ end
+ yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_pipeline.yaml.erb"))).result(binding)
+ pipeline_yaml = yaml.gsub(/^\s*\n/, "")
+ Rails.logger.info "========================="
+ Rails.logger.info pipeline_yaml
+ pipeline_yaml
+ end
+
+ private
+
+ def get_pipeline_file_sha(file_name, branch)
+ file_path_uri = URI.parse(URI.encode(file_name))
+ interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: branch || @project.default_branch)
+ if interactor.success?
+ file = interactor.result
+ file['sha']
+ else
+ nil
+ end
+ end
+
+ def content_params(opt)
+ {
+ filepath: ".gitea/workflows/#{@pipeline.pipeline_name}.yml",
+ branch: @pipeline.branch,
+ new_branch: @pipeline.branch,
+ content: opt == "create" ? Base64.encode64(@pipeline.yaml).gsub(/\n/, '') : @pipeline.yaml,
+ message: opt == "create" ? "创建流水线:#{@pipeline.pipeline_name}" : "修改流水线:#{@pipeline.pipeline_name}",
+ committer: {
+ email: current_user.mail,
+ name: current_user.login
+ },
+ identifier: @project.identifier
+ }
+ end
+
+ def build_nodes(params_nodes)
+ steps_nodes = []
+ params_nodes.each do |input_node|
+ node = Action::Node.find_by(name: input_node["data"]["name"])
+ next if node.blank?
+ node.label = input_node["data"]["label"] if input_node["data"]["label"].present?
+ node.node_id = input_node["id"] if input_node["id"].present?
+ run_values = {}
+ input_values = {}
+ if input_node["data"]["inputs"].present?
+ input_node["data"]["inputs"].each do |input|
+ if input["name"].to_s.gsub("--", "") == "run"
+ run_values = run_values.merge({ "#{input["name"].gsub("--", "")}": "#{input["value"]}" })
+ else
+ input_values = input_values.merge({ "#{input["name"].gsub("--", "")}": "#{input["value"]}" })
+ end
+ end
+ node.run_values = run_values
+ node.input_values = input_values
+ end
+ steps_nodes.push(node)
+ end
+ steps_nodes
+ end
+
+ def get_all_child_nodes(pipeline_json, job_node, target_node)
+ job_node.sub_nodes.push(get_node_info(target_node))
+ target_edge = pipeline_json["edges"].select { |node| node["source"]["cell"].to_s.include?(target_node["id"]) }&.first
+ # 判断是否有子节点
+ if target_edge.present?
+ next_node = pipeline_json["nodes"].select { |node| node["id"].include?(target_edge["target"]["cell"]) }.first
+ get_all_child_nodes(pipeline_json, job_node, next_node)
+ end
+ end
+
+ def get_node_info(input_node)
+ node = Action::Node.find_by(name: input_node["data"]["name"])
+ return nil if node.blank?
+ node.label = input_node["data"]["label"] if input_node["data"]["label"].present?
+ node.node_id = input_node["id"] if input_node["id"].present?
+ run_values = {}
+ input_values = {}
+ if input_node["data"]["inputs"].present?
+ input_node["data"]["inputs"].each do |input|
+ if input["name"].to_s.gsub("--", "") == "run"
+ run_values = run_values.merge({ "#{input["name"].gsub("--", "")}": "#{input["value"]}" })
+ else
+ input_values = input_values.merge({ "#{input["name"].gsub("--", "")}": "#{input["value"]}" })
+ end
+ end
+ node.run_values = run_values
+ node.input_values = input_values
+ end
+ node
+ end
+
+ def demo2
+ {
+ "nodes": [
+ {
+ "position": {
+ "x": 290,
+ "y": 190
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "on-push-b2c871f-left",
+ "group": "left"
+ },
+ {
+ "id": "on-push-b2c871f-right",
+ "group": "right"
+ },
+ {
+ "id": "on-push-b2c871f-top",
+ "group": "top"
+ },
+ {
+ "id": "on-push-b2c871f-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "on-push-b2c871f",
+ "data": {
+ "id": "on-push-b2c871f",
+ "label": "代码push事件启动",
+ "name": "on-push",
+ "full_name": "on-push",
+ "description": "GitLink仓库push事件",
+ "icon": "http://172.20.32.201:4000/api/attachments/c2f255e7-f359-4085-b5ce-1f05850ad74c",
+ "action_node_types_id": 3,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "start",
+ "is_mutil_link": true,
+ "link_type": "job",
+ "inputs": [
+ {
+ "id": 6,
+ "name": "branches",
+ "input_type": "input",
+ "description": "分支名称,多个分支英文逗号隔开,如'master,dev'",
+ "is_required": true,
+ "value": "master"
+ },
+ {
+ "id": 7,
+ "name": "paths-ignore",
+ "input_type": "input",
+ "description": "忽略文件,多个文件英文逗号隔开,如'**.md,**.yaml'",
+ "is_required": false,
+ "value": "'*.md'"
+ }
+ ],
+ "x": 673,
+ "y": 495,
+ "img": "http://172.20.32.201:4000/api/attachments/c2f255e7-f359-4085-b5ce-1f05850ad74c",
+ "branches": "master",
+ "paths-ignore": "'*.md'"
+ },
+ "zIndex": 1,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 680,
+ "y": 190
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "job-ed9ed25-left",
+ "group": "left"
+ },
+ {
+ "id": "job-ed9ed25-right",
+ "group": "right"
+ },
+ {
+ "id": "job-ed9ed25-top",
+ "group": "top"
+ },
+ {
+ "id": "job-ed9ed25-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "job-ed9ed25",
+ "data": {
+ "id": "job-ed9ed25",
+ "label": "单元测试",
+ "name": "job",
+ "full_name": "",
+ "description": "允许并行多个任务",
+ "icon": "",
+ "action_node_types_id": 7,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "job",
+ "is_mutil_link": true,
+ "link_type": "job,step",
+ "inputs": [],
+ "x": 1093,
+ "y": 513,
+ "img": ""
+ },
+ "zIndex": 2,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 1090,
+ "y": 190
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "job-3359fd2c-left",
+ "group": "left"
+ },
+ {
+ "id": "job-3359fd2c-right",
+ "group": "right"
+ },
+ {
+ "id": "job-3359fd2c-top",
+ "group": "top"
+ },
+ {
+ "id": "job-3359fd2c-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "job-3359fd2c",
+ "data": {
+ "id": "job-3359fd2c",
+ "label": "压力测试",
+ "name": "job",
+ "full_name": "",
+ "description": "允许并行多个任务",
+ "icon": "",
+ "action_node_types_id": 7,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "job",
+ "is_mutil_link": true,
+ "link_type": "job,step",
+ "inputs": [],
+ "x": 1493,
+ "y": 540,
+ "img": ""
+ },
+ "zIndex": 3,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 680,
+ "y": 380
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "shell-79d5ba5d-left",
+ "group": "left"
+ },
+ {
+ "id": "shell-79d5ba5d-right",
+ "group": "right"
+ },
+ {
+ "id": "shell-79d5ba5d-top",
+ "group": "top"
+ },
+ {
+ "id": "shell-79d5ba5d-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "shell-79d5ba5d",
+ "data": {
+ "id": "shell-79d5ba5d",
+ "label": "运行Shell脚本",
+ "name": "shell",
+ "full_name": "shell",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "action_node_types_id": 4,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [
+ {
+ "id": 3,
+ "name": "run",
+ "input_type": "input",
+ "is_required": false,
+ "value": "echo 111"
+ }
+ ],
+ "x": 1190,
+ "y": 662,
+ "img": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "run": "echo 111"
+ },
+ "zIndex": 5,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 1470,
+ "y": 190
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "job-7760b89e-left",
+ "group": "left"
+ },
+ {
+ "id": "job-7760b89e-right",
+ "group": "right"
+ },
+ {
+ "id": "job-7760b89e-top",
+ "group": "top"
+ },
+ {
+ "id": "job-7760b89e-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "job-7760b89e",
+ "data": {
+ "id": "job-7760b89e",
+ "label": "功能测试",
+ "name": "job",
+ "full_name": "",
+ "description": "允许并行多个任务",
+ "icon": "",
+ "action_node_types_id": 7,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "job",
+ "is_mutil_link": true,
+ "link_type": "job,step",
+ "inputs": [],
+ "x": 1909,
+ "y": 552,
+ "img": ""
+ },
+ "zIndex": 4,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 1090,
+ "y": 370
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "shell-dd97c50-left",
+ "group": "left"
+ },
+ {
+ "id": "shell-dd97c50-right",
+ "group": "right"
+ },
+ {
+ "id": "shell-dd97c50-top",
+ "group": "top"
+ },
+ {
+ "id": "shell-dd97c50-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "shell-dd97c50",
+ "data": {
+ "id": "shell-dd97c50",
+ "label": "运行Shell脚本",
+ "name": "shell",
+ "full_name": "shell",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "action_node_types_id": 4,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [
+ {
+ "id": 3,
+ "name": "run",
+ "input_type": "input",
+ "is_required": false,
+ "value": "echo 444"
+ }
+ ],
+ "x": 1572,
+ "y": 725,
+ "img": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "run": "echo 444"
+ },
+ "zIndex": 7,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 680,
+ "y": 560
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "shell-799aadee-left",
+ "group": "left"
+ },
+ {
+ "id": "shell-799aadee-right",
+ "group": "right"
+ },
+ {
+ "id": "shell-799aadee-top",
+ "group": "top"
+ },
+ {
+ "id": "shell-799aadee-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "shell-799aadee",
+ "data": {
+ "id": "shell-799aadee",
+ "label": "唱首歌",
+ "name": "shell",
+ "full_name": "shell",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "action_node_types_id": 4,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [
+ {
+ "id": 3,
+ "name": "run",
+ "input_type": "input",
+ "is_required": false,
+ "value": "echo 222"
+ }
+ ],
+ "x": 1190,
+ "y": 922,
+ "img": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "run": "echo 222"
+ },
+ "zIndex": 6,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 1470,
+ "y": 370
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "shell-1766a765-left",
+ "group": "left"
+ },
+ {
+ "id": "shell-1766a765-right",
+ "group": "right"
+ },
+ {
+ "id": "shell-1766a765-top",
+ "group": "top"
+ },
+ {
+ "id": "shell-1766a765-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "shell-1766a765",
+ "data": {
+ "id": "shell-1766a765",
+ "label": "运行Shell脚本",
+ "name": "shell",
+ "full_name": "shell",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "action_node_types_id": 4,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [
+ {
+ "id": 3,
+ "name": "run",
+ "input_type": "input",
+ "is_required": false,
+ "value": "echo 666"
+ }
+ ],
+ "x": 1986,
+ "y": 702,
+ "img": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "run": "echo 666"
+ },
+ "zIndex": 9,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 1090,
+ "y": 550
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "shell-4e09400-left",
+ "group": "left"
+ },
+ {
+ "id": "shell-4e09400-right",
+ "group": "right"
+ },
+ {
+ "id": "shell-4e09400-top",
+ "group": "top"
+ },
+ {
+ "id": "shell-4e09400-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "shell-4e09400",
+ "data": {
+ "id": "shell-4e09400",
+ "label": "测试报告浏览",
+ "name": "shell",
+ "full_name": "shell",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "action_node_types_id": 4,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [
+ {
+ "id": 3,
+ "name": "run",
+ "input_type": "input",
+ "is_required": false,
+ "value": "echo 555"
+ }
+ ],
+ "x": 1577,
+ "y": 904,
+ "img": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "run": "echo 555"
+ },
+ "zIndex": 8,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 680,
+ "y": 760
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "shell-5cec9ec-left",
+ "group": "left"
+ },
+ {
+ "id": "shell-5cec9ec-right",
+ "group": "right"
+ },
+ {
+ "id": "shell-5cec9ec-top",
+ "group": "top"
+ },
+ {
+ "id": "shell-5cec9ec-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "shell-5cec9ec",
+ "data": {
+ "id": "shell-5cec9ec",
+ "label": "运行Shell脚本",
+ "name": "shell",
+ "full_name": "shell",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "action_node_types_id": 4,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [
+ {
+ "id": 3,
+ "name": "run",
+ "input_type": "input",
+ "is_required": false,
+ "value": "echo 333"
+ }
+ ],
+ "x": 1189,
+ "y": 1082,
+ "img": "http://172.20.32.201:4000/api/attachments/1d739f94-4e5e-41b8-be7b-13482a099c76",
+ "run": "echo 333"
+ },
+ "zIndex": 10,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ },
+ {
+ "position": {
+ "x": 1470,
+ "y": 560
+ },
+ "size": {
+ "width": 212,
+ "height": 48
+ },
+ "view": "react-shape-view",
+ "shape": "data-processing-dag-node",
+ "ports": {
+ "groups": {
+ "top": {
+ "position": {
+ "name": "top",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "bottom": {
+ "position": {
+ "name": "bottom",
+ "args": {
+ "dx": 0
+ }
+ },
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "left": {
+ "position": "left",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ },
+ "right": {
+ "position": "right",
+ "attrs": {
+ "circle": {
+ "r": 4,
+ "magnet": true,
+ "strokeWidth": 1,
+ "fill": "#fff",
+ "stroke": "#85A5FF"
+ }
+ }
+ }
+ },
+ "items": [
+ {
+ "id": "checkout-gitlink-f6e67fe-left",
+ "group": "left"
+ },
+ {
+ "id": "checkout-gitlink-f6e67fe-right",
+ "group": "right"
+ },
+ {
+ "id": "checkout-gitlink-f6e67fe-top",
+ "group": "top"
+ },
+ {
+ "id": "checkout-gitlink-f6e67fe-bottom",
+ "group": "bottom"
+ }
+ ]
+ },
+ "id": "checkout-gitlink-f6e67fe",
+ "data": {
+ "id": "checkout-gitlink-f6e67fe",
+ "label": "加速版-克隆代码",
+ "name": "checkout-gitlink",
+ "full_name": "https://gitlink.org.cn/actions/checkout@v4",
+ "description": "",
+ "icon": "http://172.20.32.201:4000/api/attachments/c2f255e7-f359-4085-b5ce-1f05850ad74c",
+ "action_node_types_id": 6,
+ "yaml": "",
+ "sort_no": 0,
+ "use_count": 0,
+ "node_type": "step",
+ "is_mutil_link": false,
+ "link_type": "step",
+ "inputs": [],
+ "x": 2017,
+ "y": 887,
+ "img": "http://172.20.32.201:4000/api/attachments/c2f255e7-f359-4085-b5ce-1f05850ad74c"
+ },
+ "zIndex": 11,
+ "isDelete": true,
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "x": "100%",
+ "y": 0
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "edges": [
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "9cd9fab0-875a-4ddc-9d32-06fb5abcd986",
+ "zIndex": -1,
+ "source": {
+ "cell": "on-push-b2c871f",
+ "port": "on-push-b2c871f-right"
+ },
+ "target": {
+ "cell": "job-ed9ed25",
+ "port": "job-ed9ed25-left"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "32fee66b-377e-49a6-9f5c-b6a7cb78b4f0",
+ "zIndex": -1,
+ "source": {
+ "cell": "job-ed9ed25",
+ "port": "job-ed9ed25-right"
+ },
+ "target": {
+ "cell": "job-3359fd2c",
+ "port": "job-3359fd2c-left"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "84b37c54-b347-4ecb-8e7d-f338bb83c503",
+ "zIndex": -1,
+ "source": {
+ "cell": "job-3359fd2c",
+ "port": "job-3359fd2c-right"
+ },
+ "target": {
+ "cell": "job-7760b89e",
+ "port": "job-7760b89e-left"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "1703563e-2233-4d23-89ec-ed61f4243791",
+ "zIndex": -1,
+ "source": {
+ "cell": "job-ed9ed25",
+ "port": "job-ed9ed25-bottom"
+ },
+ "target": {
+ "cell": "shell-79d5ba5d",
+ "port": "shell-79d5ba5d-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "0be52bf9-8be4-43a9-aaca-1632eaf450ce",
+ "zIndex": -1,
+ "source": {
+ "cell": "shell-79d5ba5d",
+ "port": "shell-79d5ba5d-bottom"
+ },
+ "target": {
+ "cell": "shell-799aadee",
+ "port": "shell-799aadee-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "5b9800bd-5486-4b30-9142-49e6c5a2f78b",
+ "zIndex": -1,
+ "source": {
+ "cell": "job-3359fd2c",
+ "port": "job-3359fd2c-bottom"
+ },
+ "target": {
+ "cell": "shell-dd97c50",
+ "port": "shell-dd97c50-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "0b01ccf2-0ac9-48d5-a5d1-34a4ccc3f1f7",
+ "zIndex": -1,
+ "source": {
+ "cell": "shell-dd97c50",
+ "port": "shell-dd97c50-bottom"
+ },
+ "target": {
+ "cell": "shell-4e09400",
+ "port": "shell-4e09400-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "c10ada7b-ada5-4970-b05d-ea31b97c1408",
+ "zIndex": -1,
+ "source": {
+ "cell": "job-7760b89e",
+ "port": "job-7760b89e-bottom"
+ },
+ "target": {
+ "cell": "shell-1766a765",
+ "port": "shell-1766a765-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "78909862-8e2a-4750-8b5c-c621a7c327ba",
+ "zIndex": -1,
+ "source": {
+ "cell": "shell-799aadee",
+ "port": "shell-799aadee-bottom"
+ },
+ "target": {
+ "cell": "shell-5cec9ec",
+ "port": "shell-5cec9ec-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ },
+ {
+ "shape": "data-processing-curve",
+ "inherit": "edge",
+ "connector": {
+ "name": "rounded",
+ "args": {
+ "radius": 10
+ }
+ },
+ "router": {
+ "name": "manhattan"
+ },
+ "attrs": {
+ "line": {
+ "strokeDasharray": "0"
+ }
+ },
+ "id": "cf60eaaf-3503-4c6e-96d8-ca8d273b107b",
+ "zIndex": -1,
+ "source": {
+ "cell": "shell-1766a765",
+ "port": "shell-1766a765-bottom"
+ },
+ "target": {
+ "cell": "checkout-gitlink-f6e67fe",
+ "port": "checkout-gitlink-f6e67fe-top"
+ },
+ "tools": {
+ "items": [
+ {
+ "name": "button-remove",
+ "args": {
+ "distance": -40
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ end
+
+end
diff --git a/app/controllers/api/v1/projects/pulls/pulls_controller.rb b/app/controllers/api/v1/projects/pulls/pulls_controller.rb
index 1a7c574d9..28f1cd327 100644
--- a/app/controllers/api/v1/projects/pulls/pulls_controller.rb
+++ b/app/controllers/api/v1/projects/pulls/pulls_controller.rb
@@ -6,13 +6,21 @@ class Api::V1::Projects::Pulls::PullsController < Api::V1::BaseController
@pulls = kaminari_paginate(@pulls)
end
- before_action :load_pull_request, only: [:show]
+ before_action :load_pull_request, only: [:show, :files]
def show
@result_object = Api::V1::Projects::Pulls::GetService.call(@project, @pull_request, current_user&.gitea_token)
@last_review = @pull_request.reviews.order(created_at: :desc).take
end
+ def files
+ if params[:filepath].present?
+ @result_object = $gitea_hat_client.get_repos_pulls_files_by_owner_repo_index_filepath(@project&.owner.login, @project&.identifier, @pull_request.gitea_number, CGI.escape(params[:filepath]), {query: {token: current_user&.gitea_token}})
+ else
+ @result_object = $gitea_hat_client.get_repos_pulls_files_by_owner_repo_index(@project&.owner.login, @project&.identifier, @pull_request.gitea_number, {query: {isNew: "true",token: current_user&.gitea_token, page: page, limit: limit}})
+ end
+ end
+
private
def query_params
params.permit(:status, :keyword, :priority_id, :issue_tag_id, :version_id, :reviewer_id, :sort_by, :sort_direction)
diff --git a/app/controllers/api/v1/projects_controller.rb b/app/controllers/api/v1/projects_controller.rb
index 810c40171..d6a90e14a 100644
--- a/app/controllers/api/v1/projects_controller.rb
+++ b/app/controllers/api/v1/projects_controller.rb
@@ -1,5 +1,5 @@
class Api::V1::ProjectsController < Api::V1::BaseController
- before_action :require_public_and_member_above, only: [:show, :compare, :blame]
+ before_action :require_public_and_member_above, only: [:show, :compare, :blame, :sonar_search]
def index
render_ok
@@ -9,6 +9,7 @@ class Api::V1::ProjectsController < Api::V1::BaseController
@result_object = Api::V1::Projects::GetService.call(@project, current_user.gitea_token)
end
+
def compare
@result_object = Api::V1::Projects::CompareService.call(@project, params[:from], params[:to], current_user&.gitea_token)
end
diff --git a/app/controllers/api/v1/sonarqubes_controller.rb b/app/controllers/api/v1/sonarqubes_controller.rb
new file mode 100644
index 000000000..c6f718de0
--- /dev/null
+++ b/app/controllers/api/v1/sonarqubes_controller.rb
@@ -0,0 +1,171 @@
+class Api::V1::SonarqubesController < Api::V1::BaseController
+ before_action :load_repository
+ def sonar_initialize
+ gitea_params = { has_actions: params[:has_actions] == 'true' ? true :false }
+ gitea_setting = Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params)
+ if gitea_setting['has_actions'] == true
+ Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_HOST_URL', Rails.application.config_for(:configuration)['sonarqube']['url'] ).call
+ Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_TOKEN', Rails.application.config_for(:configuration)['sonarqube']['secret'] ).call
+ else
+ Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_HOST_URL', Rails.application.config_for(:configuration)['sonarqube']['url'] ).destroy
+ Gitea::Repository::ActionSecretsService.new(@owner, @project.identifier, 'SONAR_TOKEN', Rails.application.config_for(:configuration)['sonarqube']['secret'] ).destroy
+ end
+ @project.update(gitea_params)
+ render_ok
+ end
+
+ def insert_file
+ checkout_url = 'https://gitlink.org.cn/KingChan/checkout@v4'
+ scanner_url = 'https://gitlink.org.cn/KingChan/sonarqube-scan-action@master'
+ begin
+ config = Rails.application.config_for(:configuration)
+ sonarqube_config = config.dig('sonarqube')
+
+ if sonarqube_config.present? && sonarqube_config['checkout'].present?
+ checkout_url = sonarqube_config['checkout']
+ end
+ if sonarqube_config.present? && sonarqube_config['scanner'].present?
+ scanner_url = sonarqube_config['scanner']
+ end
+
+ raise 'sonar config missing' if sonarqube_config.blank?
+ rescue => ex
+ raise ex if Rails.env.production?
+
+ puts %Q{\033[33m [warning] soanrqube config or configuration.yml missing,
+ please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
+ end
+
+ sonar_scanner_content = {
+ filepath: '.gitea/workflows/SonarScanner.yaml',
+ branch: params[:branch],
+ new_branch: nil,
+ content: "
+ on:
+ # Trigger analysis when pushing to your main branches, and when creating a pull request.
+ push:
+ branches:
+ - main
+ - master
+ - develop
+ - 'releases/**'
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+ name: Main Workflow
+ jobs:
+ sonarqube:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: #{checkout_url}
+ with:
+ # Disabling shallow clones is recommended for improving the relevancy of reporting
+ fetch-depth: 0
+ - name: SonarQube Scan
+ uses: #{scanner_url}
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
+ ",
+ message: 'Add .gitea/workflows/SonarScanner.yaml',
+ committer: {
+ email: @owner.mail,
+ name: @owner.login
+ },
+ identifier: @project.identifier
+ }
+ @path = GiteaService.gitea_config[:domain]+"/#{@project.owner.login}/#{@project.identifier}/raw/branch/#{params[:branch]}/"
+ sonar_scanner_exit = Repositories::EntriesInteractor.call(@owner, @project.identifier, '.gitea/workflows/SonarScanner.yaml', ref: params[:branch])
+ if sonar_scanner_exit.success?
+ Gitea::UpdateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_scanner_content.merge(sha:sonar_scanner_exit.result['sha']))
+ else
+ sonar_scanner_content[:content] = Base64.strict_encode64(sonar_scanner_content[:content])
+ Gitea::CreateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_scanner_content)
+ end
+
+ sonar_project_content = {
+ filepath: 'sonar-project.properties',
+ branch: params[:branch],
+ new_branch: nil,
+ "content": "sonar.projectKey=#{params[:owner]}-#{@project.id}\nsonar.sources=.\nsonar.java.binaries=.",
+ "message": 'Add sonar-project.properties',
+ committer: {
+ email: @owner.mail,
+ name: @owner.login
+ },
+ identifier: @project.identifier
+ }
+ sonar_project_exit = Repositories::EntriesInteractor.call(@owner, @project.identifier, 'sonar-project.properties', ref: params[:branch])
+ if sonar_project_exit.success?
+ Gitea::UpdateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_project_content.merge(sha:sonar_project_exit.result['sha']))
+ else
+ sonar_project_content[:content] = Base64.strict_encode64(sonar_project_content[:content])
+ Gitea::CreateFileInteractor.call(@owner.gitea_token, @owner.login, sonar_project_content)
+ end
+ render_ok
+ end
+
+ def issues_search
+ params_data = {
+ components: "#{params[:owner]}-#{@project.id}",
+ s: params[:s],
+ impactSoftwareQualities: params[:impactSoftwareQualities],
+ issueStatuses: params[:issueStatuses],
+ ps: params[:ps],
+ p: params[:p],
+ facets: params[:facets],
+ additionalFields: params[:additionalFields],
+ timeZone: params[:timeZone],
+ types: params[:types],
+ impactSeverities: params[:impactSeverities],
+ tags: params[:tags]
+ }
+ data = Sonarqube.client.get('/api/issues/search', query: params_data)
+ render_ok data
+ end
+
+ def ce_component
+ params_data = {
+ components: "#{params[:owner]}-#{@project.id}",
+ }
+ data = Sonarqube.client.get('/api/ce/component', query: params_data)
+ render_ok data
+ end
+
+ def sources_issue_snippet
+ params_data = {
+ issueKey: params[:issueKey]
+ }
+ data = Sonarqube.client.get('/api/sources/issue_snippets', query: params_data)
+ render_ok data
+ end
+
+ def rules_show
+ params_data = {
+ key: params[:key]
+ }
+ data = Sonarqube.client.get('/api/rules/show', query: params_data)
+ render_ok data
+ end
+
+ def measures_search_history
+ params_data = {
+ from: params[:form],
+ component: "#{params[:owner]}-#{@project.id}",
+ metrics: params[:metrics],
+ ps: params[:ps]
+ }
+ data = Sonarqube.client.get('/api/measures/search_history', query: params_data)
+ render_ok data
+ end
+
+ def measures_component
+ params_data = {
+ component: "#{params[:owner]}-#{@project.id}",
+ additionalFields: params[:additionalFields],
+ metricKeys: params[:metricKeys]
+ }
+ data = Sonarqube.client.get('/api/measures/component', query: params_data)
+ render_ok data
+ end
+end
diff --git a/app/controllers/api/v1/users/projects_controller.rb b/app/controllers/api/v1/users/projects_controller.rb
index ac4b48324..782ed6399 100644
--- a/app/controllers/api/v1/users/projects_controller.rb
+++ b/app/controllers/api/v1/users/projects_controller.rb
@@ -8,6 +8,6 @@ class Api::V1::Users::ProjectsController < Api::V1::BaseController
private
def query_params
- params.permit(:category, :is_public, :project_type, :sort_by, :sort_direction, :search)
+ params.permit(:category, :is_public, :project_type, :sort_by, :sort_direction, :search, :start_at, :end_at)
end
end
\ No newline at end of file
diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb
index 47087c523..676304917 100644
--- a/app/controllers/api/v1/users_controller.rb
+++ b/app/controllers/api/v1/users_controller.rb
@@ -1,4 +1,5 @@
class Api::V1::UsersController < Api::V1::BaseController
+ include AesCryptHelper
before_action :load_observe_user, except: [:check_user_id, :check_user_login]
before_action :check_auth_for_observe_user, except: [:check_user_id, :check_user_login]
@@ -53,7 +54,7 @@ class Api::V1::UsersController < Api::V1::BaseController
end
def check_password
- password = params[:password]
+ password = decrypt(params[:password]) rescue ""
return tip_exception(-5, "8~16位密码,支持字母数字和符号") unless password =~ CustomRegexp::PASSWORD
return tip_exception(-5, "密码错误") unless @observe_user.check_password?(password)
render_ok
@@ -115,4 +116,29 @@ class Api::V1::UsersController < Api::V1::BaseController
return render_error('更改手机号失败!')
end
end
+
+
+ def check_user_can_delete
+ org_ids = TeamUser.where(user_id: @observe_user.id).pluck(:organization_id) | OrganizationUser.where(user_id: @observe_user.id).pluck(:organization_id)
+ org_count = TeamUser.where(organization_id: org_ids).where(user_id: @observe_user.id).joins(:team).where(teams: {authorize: %w(owner)}).count
+ project_count = Project.where(user_id: @observe_user.id).count
+ render_ok({ can_delete: org_count == 0 && project_count == 0, org_count: org_count, project_count: project_count })
+ end
+
+
+ def destroy
+ password = decrypt(params[:password]) rescue ""
+ return tip_exception(-1, "密码不正确.") unless @observe_user.check_password?(password)
+ org_ids = TeamUser.where(user_id: @observe_user.id).pluck(:organization_id) | OrganizationUser.where(user_id: @observe_user.id).pluck(:organization_id)
+ org_count = TeamUser.where(organization_id: org_ids).where(user_id: @observe_user.id).joins(:team).where(teams: {authorize: %w(owner)}).count
+ project_count = Project.where(user_id: @observe_user.id).count
+ return tip_exception(-1, "当前账号名下存在拥有的组织/代码库,请先删除或转让后再尝试注销操作.") if org_count > 0 || project_count > 0
+ UserAction.create(action_id: @observe_user.id, action_type: "DestroyUser", user_id: @observe_user.id, :ip => request.remote_ip, data_bank: @observe_user.attributes.to_json, memo: params[:memo])
+ @result_object = Api::V1::Users::DeleteUserService.call(@observe_user)
+ if @result_object
+ return render_ok
+ else
+ return render_error('删除失败!')
+ end
+ end
end
\ No newline at end of file
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 6957922d4..ea050ebe5 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -210,6 +210,10 @@ class ApplicationController < ActionController::Base
tip_exception(401, "请登录后再操作") unless User.current.logged?
end
+ def require_referer
+ tip_exception(403, "你没有权限访问") if request.host.present? && !request.referer.to_s.include?(request.host.to_s.gsub("www.",""))
+ end
+
def require_login_or_token
if params[:token].present?
user = User.try_to_autologin(params[:token])
@@ -326,19 +330,19 @@ class ApplicationController < ActionController::Base
User.current = find_current_user
uid_logger("user_setup: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
- # 开放课程通过链接访问的用户
- if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank?
- content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}"
-
- if Digest::MD5.hexdigest(content) == params[:chinaoocKey]
- user = open_class_user
- if user
- start_user_session(user)
- set_autologin_cookie(user)
- end
- User.current = user
- end
- end
+ # # 开放课程通过链接访问的用户
+ # if !User.current.logged? && !params[:chinaoocTimestamp].blank? && !params[:websiteName].blank? && !params[:chinaoocKey].blank?
+ # content = "#{OPENKEY}#{params[:websiteName]}#{params[:chinaoocTimestamp]}"
+ #
+ # if Digest::MD5.hexdigest(content) == params[:chinaoocKey]
+ # user = open_class_user
+ # if user
+ # start_user_session(user)
+ # set_autologin_cookie(user)
+ # end
+ # User.current = user
+ # end
+ # end
if !User.current.logged? && Rails.env.development?
user = User.find 1
@@ -371,15 +375,14 @@ class ApplicationController < ActionController::Base
uid_logger("user setup start: session[:user_id] is #{session[:user_id]}")
uid_logger("0000000000000user setup start: default_yun_session is #{default_yun_session}, session[:current_user_id] is #{session[:"#{default_yun_session}"]}")
current_domain_session = session[:"#{default_yun_session}"]
- if current_domain_session
- # existing session
- User.current = (User.active.find(current_domain_session) rescue nil)
- elsif autologin_user = try_to_autologin
- autologin_user
- elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
- # RSS key authentication does not start a session
- User.find_by_rss_key(params[:key])
+ autologin_user = try_to_autologin
+ uid_logger("user setup start: autologin_user is #{autologin_user}")
+ # 多浏览器退出账号时,token不存在处理
+ if current_domain_session && autologin_user.nil?
+ autologin_user = (User.active.find(current_domain_session) rescue nil)
+ set_autologin_cookie(autologin_user) if autologin_user.present?
end
+ autologin_user
end
def try_to_autologin
diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb
index c824b6ce3..e4038fafa 100644
--- a/app/controllers/attachments_controller.rb
+++ b/app/controllers/attachments_controller.rb
@@ -30,25 +30,33 @@ class AttachmentsController < ApplicationController
def get_file
+ Rails.logger.info("request.host===#{request.host},request.referer===#{request.referer}")
+ tip_exception(403, "你没有权限访问") if request.host.present? && !request.referer.to_s.include?(request.host.to_s.gsub("www.",""))
normal_status(-1, "参数缺失") if params[:download_url].blank?
url = base_url.starts_with?("https:") ? params[:download_url].to_s.gsub("http:", "https:") : params[:download_url].to_s
+ md5_file = Digest::MD5.hexdigest(params[:download_url])
+ FileUtils.mkdir_p("#{Rails.root}#{EduSetting.get("attachment_folder")}gitea/") unless Dir.exists?("#{Rails.root}#{EduSetting.get("attachment_folder")}gitea/")
+ tmp_path = "#{Rails.root}#{EduSetting.get("attachment_folder")}gitea/#{Time.now.strftime('%Y%m%d')}-#{md5_file}"
if url.starts_with?(base_url) && !url.starts_with?("#{base_url}/repo")
domain = GiteaService.gitea_config[:domain]
api_url = GiteaService.gitea_config[:base_url]
url = ("/repos"+url.split(base_url + "/api")[1])
filepath, ref = url.split("/")[-1].split("?")
url.gsub!(url.split("/")[-1], '')
- Rails.logger.info("url===#{url}")
+ # Rails.logger.info("url===#{url}")
Rails.logger.info(filepath)
- request_url = [domain, api_url, URI.encode(url), URI.escape(filepath), "?ref=#{URI.escape(ref.split('ref=')[1])}&access_token=#{User.where(admin: true).take&.gitea_token}"].join
+ ref = ref.blank? ? "" : URI.escape(ref.split('ref=')[1])
+ request_url = [domain, api_url, URI.encode(url), URI.escape(filepath), "?ref=#{ref}&access_token=#{User.where(admin: true).take&.gitea_token}"].join
Rails.logger.info("request_url===#{request_url}")
- response = Faraday.get(request_url)
+ File.delete(tmp_path) if File.exist?(tmp_path) # 删除之前的文件
+ Util.download_file(request_url, tmp_path)
filename = filepath
else
- response = Faraday.get(URI.encode(url))
+ File.delete(tmp_path) if File.exist?(tmp_path) # 删除之前的文件
+ Util.download_file(URI.encode(url), tmp_path)
filename = params[:download_url].to_s.split("/").pop()
end
- send_data(response.body.force_encoding("UTF-8"), filename: filename, type: "application/octet-stream", disposition: 'attachment')
+ send_file(tmp_path, filename: filename, type: "application/octet-stream", disposition: 'attachment')
end
def create
diff --git a/app/controllers/commit_logs_controller.rb b/app/controllers/commit_logs_controller.rb
index 3c0ad6bf4..8c2b144ac 100644
--- a/app/controllers/commit_logs_controller.rb
+++ b/app/controllers/commit_logs_controller.rb
@@ -26,6 +26,16 @@ class CommitLogsController < ApplicationController
commit_log.project_trends.create(user_id: user.id, project_id: project&.id, action_type: "create") if user.id !=2
# 统计数据新增
CacheAsyncSetJob.perform_later("project_common_service", {commits: 1}, project.id)
+
+ commit_user = User.find_by(mail: commit[:committer][:email]) rescue nil
+ commit_user = User.find_by(login: commit[:committer][:name]) if commit_user.blank? rescue nil
+ next if commit_user.blank?
+
+ # 触发变更issue状态的job
+ close_issue_content = message.to_s.scan(/\b(Close|Closes|Closed|Closing|close|closes|closed|closing)\s*(#\d+(,\s*#\d+)*)?\b/)
+ ChangeIssueStatusByMessageJob.perform_later(commit_id, project, commit_user, close_issue_content[0][1], 5) if close_issue_content[0].present? && close_issue_content[0][1].present?
+ solve_issue_content = message.to_s.scan(/\b(Fix|Fixes|Fixed|Fixing|fix|fixes|fixed|fixing|Resolve|Resolves|Resolved|Resolving|resolve|resolves|resolved|resolving|Implement|Implements|Implemented|Implementing|implement|implements|implemented|implementing)\s*(#\d+(,\s*#\d+)*)?\b/)
+ ChangeIssueStatusByMessageJob.perform_later(commit_id, project, commit_user, solve_issue_content[0][1], 3) if solve_issue_content[0].present? && solve_issue_content[0][1].present?
end
end
diff --git a/app/controllers/compare_controller.rb b/app/controllers/compare_controller.rb
index afdceac9d..a007b1178 100644
--- a/app/controllers/compare_controller.rb
+++ b/app/controllers/compare_controller.rb
@@ -18,6 +18,7 @@ class CompareController < ApplicationController
@page_limit = page_limit <=0 ? 15 : page_limit
@page_offset = (@page_size -1) * @page_limit
Rails.logger.info("+========#{@page_size}-#{@page_limit}-#{@page_offset}")
+ Rails.logger.info @compare_result
end
private
@@ -36,7 +37,7 @@ class CompareController < ApplicationController
if @exist_pullrequest.present?
return -2, "在这些分支之间的合并请求已存在:#{@exist_pullrequest.try(:title)}"
else
- if @compare_result["Commits"].blank? && @compare_result["Diff"].blank?
+ if @compare_result["FilesCount"].to_i == 0 && @compare_result["CommitsCount"].to_i == 0
return -2, "分支内容相同,无需创建合并请求"
end
end
diff --git a/app/controllers/concerns/api/pull_helper.rb b/app/controllers/concerns/api/pull_helper.rb
index e186cd6f2..ab72a0080 100644
--- a/app/controllers/concerns/api/pull_helper.rb
+++ b/app/controllers/concerns/api/pull_helper.rb
@@ -3,7 +3,7 @@ module Api::PullHelper
def load_pull_request
pull_request_id = params[:pull_id] || params[:id]
- @pull_request = @project.pull_requests.where(gitea_number: pull_request_id).where.not(id: pull_request_id).take || PullRequest.find_by_id(pull_request_id)
+ @pull_request = @project.pull_requests.where(gitea_number: pull_request_id).where.not(id: pull_request_id).take || @project.pull_requests.find_by_id(pull_request_id)
@issue = @pull_request&.issue
if @pull_request
logger.info "###########pull_request founded"
diff --git a/app/controllers/concerns/git_helper.rb b/app/controllers/concerns/git_helper.rb
index ede90dc6c..9a48e5ed5 100644
--- a/app/controllers/concerns/git_helper.rb
+++ b/app/controllers/concerns/git_helper.rb
@@ -77,4 +77,12 @@ module GitHelper
cha_path = path.present? ? path.split(";") : []
cha_path.reject(&:blank?)[0].try(:strip)
end
+
+ def expain_issue_commit(commit_message)
+ respace_arr= commit_message.to_s.scan(/#(\d+)/).map{|s|[s[0], "##{s[0]}"]}.uniq.sort_by{|s|-s[0].to_i}
+ respace_arr.each do |item|
+ issue = Issue.find_by_id(item[0].to_i)
+
+ end
+ end
end
\ No newline at end of file
diff --git a/app/controllers/concerns/login_helper.rb b/app/controllers/concerns/login_helper.rb
index c0e8d01c0..c3b3ecc92 100644
--- a/app/controllers/concerns/login_helper.rb
+++ b/app/controllers/concerns/login_helper.rb
@@ -73,6 +73,17 @@ module LoginHelper
session[:"#{default_yun_session}"] = nil
end
+ def clear_user_cookie
+ if edu_setting('cookie_domain').present?
+ cookies.delete(autologin_cookie_name, domain: edu_setting('cookie_domain'))
+ else
+ cookies.delete(autologin_cookie_name)
+ end
+ # 清除前端写入的用户名
+ Rails.logger.info("########________cookies['login']___________###########{cookies['login']}")
+ cookies.delete("login")
+ end
+
# Sets the logged in user
def logged_user=(user)
reset_session
diff --git a/app/controllers/mark_files_controller.rb b/app/controllers/mark_files_controller.rb
index a7ec304fd..f21b759fb 100644
--- a/app/controllers/mark_files_controller.rb
+++ b/app/controllers/mark_files_controller.rb
@@ -50,7 +50,7 @@ class MarkFilesController < ApplicationController
end
def load_pull_request
- @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || PullRequest.find_by_id(params[:id])
+ @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || @project.pull_requests.find_by_id(params[:id])
end
end
\ No newline at end of file
diff --git a/app/controllers/oauth/ci4s_controller.rb b/app/controllers/oauth/ci4s_controller.rb
new file mode 100644
index 000000000..a36454562
--- /dev/null
+++ b/app/controllers/oauth/ci4s_controller.rb
@@ -0,0 +1,53 @@
+class Oauth::Ci4sController < Oauth::BaseController
+ include RegisterHelper
+
+
+ def oauth_url
+ redirect_to Ci4s::Service.oauth_url
+ end
+
+ # 需要educoder那边设置回调地址
+ def create
+ begin
+ code = params['code'].to_s.strip
+ tip_exception("code不能为空") if code.blank?
+
+ new_user = false
+ token = Ci4s::Service.access_token(code)
+ Rails.logger.info("[OAuth2] result -> #{token}")
+ result = Ci4s::Service.user_info(token[:access_token])
+ tip_exception("请求用户信息错误") if result['code'].to_i != 200
+ user_info = result['data']
+ # 存在该用户
+ open_user = OpenUsers::Ci4s.find_by(uid: user_info['username'])
+ if open_user.present? && open_user.user.present?
+ successful_authentication(open_user.user)
+ redirect_to root_path(new_user: false)
+ return
+ else
+ if current_user.blank? || !current_user.logged?
+ new_user = true
+ session[:unionid] = user_info['username']
+ # login = User.generate_login('E')
+ login = user_info['username']
+ email = user_info['email']
+ email = "#{login}@forge.com" if email.blank?
+ reg_result = autologin_register(login, email, "Ec#{login}2021#", 'educoder', user_info['mobile'])
+ Rails.logger.info("[OAuth2] reg_result -> #{reg_result}")
+ if reg_result[:message].blank?
+ open_user = OpenUsers::Ci4s.create!(user_id: reg_result[:user][:id], uid: login, extra: user_info)
+ successful_authentication(open_user.user)
+ else
+ tip_exception(reg_result[:message])
+ end
+ else
+ OpenUsers::Ci4s.create!(user: current_user, uid: user_info['username'], extra: user_info)
+ end
+ end
+ Rails.logger.info("[OAuth2] session[:unionid] -> #{session[:unionid]}")
+ redirect_to root_path(new_user: new_user)
+ rescue Exception => ex
+ render_error(ex.message)
+ end
+ end
+end
diff --git a/app/controllers/oauth/educoder_controller.rb b/app/controllers/oauth/educoder_controller.rb
index 6d479ed0c..23dde591f 100644
--- a/app/controllers/oauth/educoder_controller.rb
+++ b/app/controllers/oauth/educoder_controller.rb
@@ -45,6 +45,9 @@ class Oauth::EducoderController < Oauth::BaseController
result = EducoderOauth::Service.access_token(code)
result = EducoderOauth::Service.user_info(result[:access_token])
+ Rails.logger.info("OAuth2-session-unionid====111=======#{result['login']}")
+ tip_exception("调用头歌接口错误") if result['login'].blank?
+
# 存在该用户
open_user = OpenUsers::Educoder.find_by(uid: result['login'])
if open_user.present? && open_user.user.present?
@@ -68,7 +71,7 @@ class Oauth::EducoderController < Oauth::BaseController
OpenUsers::Educoder.create!(user: current_user, uid: result['login'], extra: result)
end
end
- Rails.logger.info("[OAuth2] session[:unionid] -> #{session[:unionid]}")
+ Rails.logger.info("OAuth2-session-unionid=========== #{session[:unionid]}")
redirect_to "/bindlogin/educoder"
# redirect_to root_path(new_user: new_user)
diff --git a/app/controllers/oauth2_controller.rb b/app/controllers/oauth2_controller.rb
index 9be575a1f..11ebe587a 100644
--- a/app/controllers/oauth2_controller.rb
+++ b/app/controllers/oauth2_controller.rb
@@ -1,4 +1,4 @@
-class Oauth2Controller < ActionController::Base
+class Oauth2Controller < ApplicationController
layout 'doorkeeper/application'
include LoginHelper
@@ -6,6 +6,9 @@ class Oauth2Controller < ActionController::Base
client_id = params[:call_url].split("client_id=")[1].split("&redirect_uri")[0]
@call_url = request.fullpath.split('call_url=').last
@app = Doorkeeper::Application.find_by(uid: client_id)
+ if User.current.logged?
+ redirect_to @call_url + "&auth=" + User.current.login
+ end
end
def create
diff --git a/app/controllers/organizations/organizations_controller.rb b/app/controllers/organizations/organizations_controller.rb
index 3c4e64780..6cedea496 100644
--- a/app/controllers/organizations/organizations_controller.rb
+++ b/app/controllers/organizations/organizations_controller.rb
@@ -1,4 +1,5 @@
class Organizations::OrganizationsController < Organizations::BaseController
+ include AesCryptHelper
before_action :require_login, except: [:index, :show, :recommend, :languages]
# before_action :require_profile_completed, only: [:create]
before_action :convert_image!, only: [:create, :update]
@@ -139,7 +140,7 @@ class Organizations::OrganizationsController < Organizations::BaseController
end
def password
- params.fetch(:password, "")
+ decrypt(params[:password]) rescue ""
end
def load_organization
diff --git a/app/controllers/organizations/projects_controller.rb b/app/controllers/organizations/projects_controller.rb
index ab5c9ef5d..380f32e48 100644
--- a/app/controllers/organizations/projects_controller.rb
+++ b/app/controllers/organizations/projects_controller.rb
@@ -10,6 +10,9 @@ class Organizations::ProjectsController < Organizations::BaseController
@projects = Project.from("( #{ public_projects_sql} UNION #{ private_projects_sql } ) AS projects")
# 表情处理
keywords = params[:search].to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
+ @projects = @projects.where(id: params[:pm_project_repository_ids].split(',')) if params[:pm_project_repository_ids].present?
+ @projects = @projects.where.not(id: params[:exclude_ids].to_s.split(",")) if params[:exclude_ids].present?
+ @projects = @projects.where(project_type: ['mirror', 'common']).where("gpid is not null") if params[:actived].present?
@projects = @projects.ransack(name_or_identifier_cont: keywords).result if params[:search].present?
@projects = @projects.includes(:owner).order("projects.#{sort} #{sort_direction}")
@projects = paginate(@projects)
diff --git a/app/controllers/owners_controller.rb b/app/controllers/owners_controller.rb
index 28a5210d5..ad3a70423 100644
--- a/app/controllers/owners_controller.rb
+++ b/app/controllers/owners_controller.rb
@@ -11,7 +11,10 @@ class OwnersController < ApplicationController
end
def show
- @owner = Owner.find_by(login: params[:id]) || Owner.find_by(id: params[:id])
+ # login = params[:id].to_s[0..-6]
+ login = params[:id].to_s
+ @owner = Owner.find_by(login: login) || Owner.find_by(id: login)
+ clear_user_cookie unless @owner.present?
return render_not_found unless @owner.present?
# 组织
if @owner.is_a?(Organization)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index d3e308450..dfe8cf3b4 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -43,11 +43,11 @@ class ProjectsController < ApplicationController
category_id = params[:category_id]
@total_count =
- if category_id.blank? && params[:search].blank? && params[:topic_id].blank?
+ if category_id.blank? && params[:search].blank? && params[:topic_id].blank? && params[:topic_name].blank?
# 默认查询时count性能问题处理
not_category_count = Project.where(project_category_id: nil).count
ProjectCategory.sum("projects_count") - Project.visible.joins("left join organization_extensions on organization_extensions.organization_id = projects.user_id").where("organization_extensions.visibility =2").count + not_category_count
- elsif params[:search].present? || params[:topic_id].present?
+ elsif params[:search].present? || params[:topic_id].present? || params[:topic_name].present?
@projects.total_count
else
cate = ProjectCategory.find_by(id: category_id)
@@ -250,18 +250,10 @@ class ProjectsController < ApplicationController
def show
end
- def mp_show
- @project = Project.joins(:owner).find params[:project_id]
- data={
- owner:@project.owner.try(:login),
- identifier:@project.identifier
- }
- render_ok(data:data)
- end
-
def destroy
if current_user.admin? || @project.manager?(current_user)
ActiveRecord::Base.transaction do
+ UserAction.create(action_id: @project.id, action_type: "DestroyProject", user_id: current_user.id, :ip => request.remote_ip, data_bank: @project.attributes.to_json)
close_fork_pull_requests_by(@project)
Gitea::Repository::DeleteService.new(@project.owner, @project.identifier,current_user.gitea_token).call
@project.destroy!
@@ -295,13 +287,19 @@ class ProjectsController < ApplicationController
end
def watch_users
+ start_at = params[:start_at]
+ end_at = params[:end_at]
watchers = @project.watchers.includes(:user).order("watchers.created_at desc").distinct
+ watchers = watchers.where("watchers.created_at > ? and watchers.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
@watchers_count = watchers.size
@watchers = paginate(watchers)
end
def praise_users
+ start_at = params[:start_at]
+ end_at = params[:end_at]
praises = @project.praise_treads.includes(:user).order("praise_treads.created_at desc").distinct
+ praises = praises.where("praise_treads.created_at > ? and praise_treads.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
@praises_count = praises.size
@praises = paginate(praises)
end
@@ -314,8 +312,8 @@ class ProjectsController < ApplicationController
def simple
if !@project.common? && @project&.repository&.mirror&.waiting?
- gitea_result = $gitea_client.get_repos_by_owner_repo(@project&.owner&.login, @project&.identifier)
- if !gitea_result["empty"]
+ gitea_result = $gitea_client.get_repos_by_owner_repo(@project&.owner&.login, @project&.identifier) rescue nil
+ if gitea_result.present? && !gitea_result["empty"]
@project&.update_columns(gpid: gitea_result["id"])
@project&.repository&.mirror&.succeeded!
project_id = @project&.id
@@ -374,7 +372,7 @@ class ProjectsController < ApplicationController
if @project_detail.save!
attachment_ids = Array(params[:attachment_ids])
logger.info "=============> #{Array(params[:attachment_ids])}"
- @attachments = Attachment.where(id: attachment_ids)
+ @attachments = Attachment.where(id: attachment_ids).or(Attachment.where(uuid: attachment_ids))
@attachments.update_all(
container_id: @project_detail.id,
container_type: @project_detail.model_name.name,
@@ -395,7 +393,7 @@ class ProjectsController < ApplicationController
end
def mirror_params
- params.permit(:user_id, :name, :description, :repository_name, :is_mirror, :auth_username, :auth_token,
+ params.permit(:user_id, :name, :description, :repository_name, :is_mirror, :auth_username, :auth_token, :service,
:auth_password, :project_category_id, :project_language_id, :clone_addr, :private)
end
diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb
index c3787c002..290862ca5 100644
--- a/app/controllers/pull_requests_controller.rb
+++ b/app/controllers/pull_requests_controller.rb
@@ -296,11 +296,11 @@ class PullRequestsController < ApplicationController
private
def load_pull_request
- @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || PullRequest.find_by_id(params[:id])
+ @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || @project.pull_requests.find_by_id(params[:id])
end
def find_pull_request
- @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || PullRequest.find_by_id(params[:id])
+ @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || @project.pull_requests.find_by_id(params[:id])
@issue = @pull_request&.issue
if @pull_request.blank?
normal_status(-1, "合并请求不存在")
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 536bda3a8..c9816ef48 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -13,6 +13,7 @@ class RepositoriesController < ApplicationController
before_action :get_ref, only: %i[entries sub_entries top_counts files archive]
before_action :get_latest_commit, only: %i[entries sub_entries top_counts]
before_action :get_statistics, only: %i[top_counts]
+ before_action :require_referer, only: [:raw]
def files
result = @project.educoder? ? nil : Gitea::Repository::Files::GetService.call(@owner, @project.identifier, @ref, params[:search], @owner.gitea_token)
@@ -150,8 +151,8 @@ class RepositoriesController < ApplicationController
return render_error('暂未开放,敬请期待.')
else
@commit = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, @owner&.gitea_token)
- @commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, @owner&.gitea_token, {diff: true})
- render_error(@commit[:message], @commit[:status]) if @commit.has_key?(:status) || @commit_diff.has_key?(:status)
+ # @commit_diff = Gitea::Repository::Commits::GetService.call(@owner.login, @repository.identifier, @sha, @owner&.gitea_token, {diff: true})
+ render_error(@commit[:message], @commit[:status]) if @commit.has_key?(:status)#|| @commit_diff.has_key?(:status)
end
end
diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb
index b5fedfae0..8ad90e5f0 100644
--- a/app/controllers/reviews_controller.rb
+++ b/app/controllers/reviews_controller.rb
@@ -14,7 +14,7 @@ class ReviewsController < ApplicationController
end
def load_pull_request
- @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || PullRequest.find_by_id(params[:id])
+ @pull_request = @project.pull_requests.where(gitea_number: params[:id]).where.not(id: params[:id]).take || @project.pull_requests.find_by_id(params[:id])
end
end
\ No newline at end of file
diff --git a/app/controllers/site_pages_controller.rb b/app/controllers/site_pages_controller.rb
index 085f1b027..408add73a 100644
--- a/app/controllers/site_pages_controller.rb
+++ b/app/controllers/site_pages_controller.rb
@@ -54,9 +54,9 @@ class SitePagesController < ApplicationController
user = User.find_by_login params[:repository][:owner][:login]
return normal_status(-1, '你还未开通Page服务,无法进行部署') unless user.website_permission
- project = Project.where(identifier: params[:repository][:name],user_id: user.id)
- return normal_status(-1, '你没有权限操作') if project.owner?(user)
- return normal_status(-1, '该仓库还未开通Page服务,无法进行部署') if Page.exists?(user: user, project: project)
+ project = Project.find_by(identifier: params[:repository][:name],user_id: user.id)
+ return normal_status(-1, '项目不存在') if project.nil?
+ return normal_status(-1, '该仓库还未开通Page服务,无法进行部署') unless Page.exists?(user: user, project: project)
@page = Page.find_by(user: user, project: project)
response_str = @page.deploy_page(branch)
diff --git a/app/controllers/users/projects_controller.rb b/app/controllers/users/projects_controller.rb
index 8ffa8fa85..55ff17192 100644
--- a/app/controllers/users/projects_controller.rb
+++ b/app/controllers/users/projects_controller.rb
@@ -20,6 +20,6 @@ class Users::ProjectsController < Users::BaseController
private
def query_params
- params.permit(:category, :status, :sort_direction)
+ params.permit(:category, :status, :sort_direction, :topic_name)
end
end
\ No newline at end of file
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 27895a758..761a88fa8 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -714,6 +714,7 @@ class UsersController < ApplicationController
private
def load_user
@user = User.find_by_login(params[:id]) || User.find_by(id: params[:id])
+ clear_user_cookie unless @user.present?
end
def user_params
diff --git a/app/controllers/watchers_controller.rb b/app/controllers/watchers_controller.rb
index aa3642616..b0305ce81 100644
--- a/app/controllers/watchers_controller.rb
+++ b/app/controllers/watchers_controller.rb
@@ -5,7 +5,10 @@ class WatchersController < ApplicationController
before_action :get_target
def index
+ start_at = params[:start_at]
+ end_at = params[:end_at]
scope = @target.watchers.includes(:user)
+ scope = scope.where("watchers.created_at > ? and watchers.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
@watchers = paginate(scope)
end
diff --git a/app/forms/base_form.rb b/app/forms/base_form.rb
index 19af61026..eb00ec562 100644
--- a/app/forms/base_form.rb
+++ b/app/forms/base_form.rb
@@ -1,5 +1,6 @@
class BaseForm
include ActiveModel::Model
+ include AesCryptHelper
Error = Class.new(StandardError)
EmailError = Class.new(Error)
@@ -53,11 +54,15 @@ class BaseForm
end
def check_password(password)
+ password = decrypt(password) rescue ""
password = strip(password)
raise PasswordFormatError, "密码8~16位密码,支持字母数字和符号" unless password =~ CustomRegexp::PASSWORD
end
def check_password_confirmation(password, password_confirmation)
+ password = decrypt(password) rescue ""
+ password_confirmation = decrypt(password_confirmation) rescue ""
+
password = strip(password)
password_confirmation = strip(password_confirmation)
diff --git a/app/forms/projects/migrate_form.rb b/app/forms/projects/migrate_form.rb
index 8c1d04968..1a1c5c389 100644
--- a/app/forms/projects/migrate_form.rb
+++ b/app/forms/projects/migrate_form.rb
@@ -1,5 +1,5 @@
class Projects::MigrateForm < BaseForm
- attr_accessor :user_id, :name, :repository_name, :project_category_id, :description, :auth_token,
+ attr_accessor :user_id, :name, :repository_name, :project_category_id, :description, :auth_token, :service,
:project_language_id, :clone_addr, :private, :is_mirror, :auth_username, :auth_password, :owner
validates :user_id, :name, :repository_name, :clone_addr, presence: true
diff --git a/app/helpers/aes_crypt_helper.rb b/app/helpers/aes_crypt_helper.rb
new file mode 100644
index 000000000..798c6dd61
--- /dev/null
+++ b/app/helpers/aes_crypt_helper.rb
@@ -0,0 +1,45 @@
+module AesCryptHelper
+
+ AES_KEY = EduSetting.get("login_crypt_key") || '59c96c3572ab8cc1'
+
+ def encrypt(plain_text, output_encoding = 'base64')
+
+ # 将字符串密钥和IV转换为16字节的字节数组
+ key = AES_KEY.byteslice(0, 16)
+ iv = AES_KEY.byteslice(0, 16)
+
+ # 创建并设置AES-CBC加密器
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ cipher.key = key
+ cipher.iv = iv
+
+ # 加密数据,并添加PKCS7填充
+ encrypted_data = cipher.update(plain_text) + cipher.final
+ # 将加密数据转换为Base64编码
+ Base64.strict_encode64(encrypted_data)
+ end
+
+ def decrypt(cipher_text, input_encoding = 'base64')
+ # 确保密钥是16字节长
+ key = AES_KEY.byteslice(0, 16) # 如果密钥不足16字节,填充空格;如果超过,截断
+ iv = AES_KEY.byteslice(0, 16)
+
+ decipher = OpenSSL::Cipher.new('AES-128-CBC')
+ decipher.decrypt
+ decipher.key = key
+ decipher.iv = iv
+
+ # 根据输入编码解码密文
+ decrypted_data = case input_encoding
+ when 'base64'
+ Base64.strict_decode64(cipher_text)
+ else
+ cipher_text
+ end
+
+ decrypted = decipher.update(decrypted_data) + decipher.final
+ decrypted
+ end
+
+end
\ No newline at end of file
diff --git a/app/helpers/api/pm/issue_links_helper.rb b/app/helpers/api/pm/issue_links_helper.rb
new file mode 100644
index 000000000..ff7d1ef33
--- /dev/null
+++ b/app/helpers/api/pm/issue_links_helper.rb
@@ -0,0 +1,2 @@
+module Api::Pm::IssueLinksHelper
+end
diff --git a/app/helpers/api/pm/projects_helper.rb b/app/helpers/api/pm/projects_helper.rb
new file mode 100644
index 000000000..172c270e7
--- /dev/null
+++ b/app/helpers/api/pm/projects_helper.rb
@@ -0,0 +1,2 @@
+module Api::Pm::ProjectsHelper
+end
diff --git a/app/helpers/api/v1/pm_issues_helper.rb b/app/helpers/api/v1/pm_issues_helper.rb
new file mode 100644
index 000000000..ced4b55c9
--- /dev/null
+++ b/app/helpers/api/v1/pm_issues_helper.rb
@@ -0,0 +1,2 @@
+module Api::V1::PmIssuesHelper
+end
diff --git a/app/helpers/api/v1/sonarqube/issues_helper.rb b/app/helpers/api/v1/sonarqube/issues_helper.rb
new file mode 100644
index 000000000..7dbee46e4
--- /dev/null
+++ b/app/helpers/api/v1/sonarqube/issues_helper.rb
@@ -0,0 +1,2 @@
+module Api::V1::Sonarqube::IssuesHelper
+end
diff --git a/app/helpers/api/v1/sonarqubes_helper.rb b/app/helpers/api/v1/sonarqubes_helper.rb
new file mode 100644
index 000000000..94205dc10
--- /dev/null
+++ b/app/helpers/api/v1/sonarqubes_helper.rb
@@ -0,0 +1,2 @@
+module Api::V1::SonarqubesHelper
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d670e9a0d..bab9ee9b2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -146,13 +146,13 @@ module ApplicationHelper
# 用户图像url,如果不存在的话,source为匿名用户,即默认使用匿名用户图像
def url_to_avatar(source)
if File.exist?(disk_filename(source&.class, source&.id))
- ctime = File.ctime(disk_filename(source.class, source.id)).to_i
- if %w(User Organization).include?(source.class.to_s)
- File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
+ ctime = File.ctime(disk_filename(source&.class, source&.id)).to_i
+ if %w(User Organization).include?(source&.class.to_s)
+ File.join("images", relative_path, ["#{source&.class}", "#{source&.id}"]) + "?t=#{ctime}"
else
- File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
+ File.join("images/avatars", ["#{source&.class}", "#{source&.id}"]) + "?t=#{ctime}"
end
- elsif source.class.to_s == 'User'
+ elsif source&.class.to_s == 'User'
source.get_letter_avatar_url
end
end
diff --git a/app/helpers/avatar_helper.rb b/app/helpers/avatar_helper.rb
index b703e1b4e..ad0ec0cde 100644
--- a/app/helpers/avatar_helper.rb
+++ b/app/helpers/avatar_helper.rb
@@ -13,13 +13,13 @@ module AvatarHelper
def url_to_avatar(source)
if File.exist?(disk_filename(source&.class, source&.id))
- ctime = File.ctime(disk_filename(source.class, source.id)).to_i
- if %w(User Organization).include?(source.class.to_s)
- File.join("images", relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
+ ctime = File.ctime(disk_filename(source&.class, source&.id)).to_i
+ if %w(User Organization).include?(source&.class.to_s)
+ File.join("images", relative_path, ["#{source&.class}", "#{source&.id}"]) + "?t=#{ctime}"
else
- File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
+ File.join("images/avatars", ["#{source&.class}", "#{source&.id}"]) + "?t=#{ctime}"
end
- elsif source.class.to_s == 'User'
+ elsif source&.class.to_s == 'User'
source.get_letter_avatar_url
end
end
diff --git a/app/helpers/pm/journals_helper.rb b/app/helpers/pm/journals_helper.rb
new file mode 100644
index 000000000..e1a99e5ee
--- /dev/null
+++ b/app/helpers/pm/journals_helper.rb
@@ -0,0 +1,2 @@
+module Pm::JournalsHelper
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0f34b7c1b..68baef400 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -129,7 +129,7 @@ module ProjectsHelper
end
def jianmu_devops_url
- EduSetting.get("jianmu_devops_url") || "https://ci-v3.test.jianmuhub.com"
+ EduSetting.get("jianmu_devops_url")
end
@@ -145,15 +145,18 @@ module ProjectsHelper
url = EduSetting.get("ai_shang_url") || "https://shang.gitlink.org.cn"
case project.identifier.to_s.downcase
when nil then ""
- when 'rails' then "#{url}/v1/rails/entropy"
- when 'jittor' then "#{url}/v1/jittor/entropy"
- when 'paddle' then "#{url}/v1/Paddle/entropy"
- when 'vue' then "#{url}/v1/vue/entropy"
- when 'bootstrap' then "#{url}/v1/bootstrap/entropy"
- when 'tensorflow' then "#{url}/v1/tensorflow/entropy"
- when 'kernel' then "#{url}/v1/openeuler/entropy"
- when 'opengauss-server' then "#{url}/v1/opengauss/entropy"
- when 'mindspore' then "#{url}/v1/mindspore/entropy"
+ # when 'rails' then "#{url}/v1/rails/entropy"
+ # when 'jittor' then "#{url}/v1/jittor/entropy"
+ # when 'paddle' then "#{url}/v1/Paddle/entropy"
+ # when 'vue' then "#{url}/v1/vue/entropy"
+ # when 'bootstrap' then "#{url}/v1/bootstrap/entropy"
+ # when 'tensorflow' then "#{url}/v1/tensorflow/entropy"
+ # when 'kernel' then "#{url}/v1/openeuler/entropy"
+ # when 'opengauss-server' then "#{url}/v1/opengauss/entropy"
+ # when 'mindspore' then "#{url}/v1/mindspore/entropy"
+ when 'openharmony-kernel' then "#{url}/api/openharmony/entropy"
+ when 'kernel' then "#{url}/api/openeuler/entropy"
+ when 'xiuos' then "#{url}/api/xiuos/entropy"
else ''
end
end
diff --git a/app/jobs/change_issue_status_by_message_job.rb b/app/jobs/change_issue_status_by_message_job.rb
new file mode 100644
index 000000000..b2ded7dec
--- /dev/null
+++ b/app/jobs/change_issue_status_by_message_job.rb
@@ -0,0 +1,46 @@
+class ChangeIssueStatusByMessageJob < ApplicationJob
+ queue_as :notify
+
+ # Close, Closes, Closed, Closing, close, closes, closed, closing
+ # Fix, Fixes, Fixed, Fixing, fix, fixes, fixed, fixing
+ # Resolve, Resolves, Resolved, Resolving, resolve, resolves, resolved, resolving
+ # Implement, Implements, Implemented, Implementing, implement, implements, implemented, implementing
+ # 以上关键词后接 issue_id 例如:Closes #234 Closes #123, #245, #992
+
+
+ def get_pm_issue_data(user, org, pm_project_id, issue_id)
+ url = "#{EduSetting.get("pms_server_url")}/api/pms/#{org.login}/pmsProjectIssues/#{issue_id}?pmProjectId=#{pm_project_id}"
+
+ headers = {
+ 'Cookie' => "autologin_trustie=#{Token.get_or_create_permanent_login_token(user, 'autologin')&.value}",
+ }
+
+ response = RestClient.get(url, headers)
+
+ puts response.body
+ return JSON.parse(response.body)["code"].to_i == 200
+ rescue
+ return false
+ end
+
+
+ def perform(commitsha, project, user, tag_issue_id_content, status_id=1)
+ Rails.logger.info "需要操作的issue_id内容为 #{tag_issue_id_content}"
+ tag_issue_id_content = tag_issue_id_content.gsub(/\s+/, '')
+ tag_issue_id_content.to_s.split(",").each do |tag_issue|
+ issue_id = tag_issue.gsub('#', '')
+ issue = project.issues.issue_issue.where(project_issues_index: issue_id).where.not(id: issue_id).take || Issue.issue_issue.find_by_id(issue_id)
+ next unless issue.present? # issue不存在 跳过
+ next if issue.project.present? && !issue.project.member?(user) # issue归属项目,用户没有修改issue的权限,跳过
+ next if issue.pm_project_id.nil? && project.id.to_i != issue.project&.id.to_i
+ next if issue.pm_project_id.present? && !get_pm_issue_data(user, project.owner, issue.pm_project_id, issue.id) # issue是组织下工作项,不具备组织的访问权限,跳过
+
+ issue_project = issue.project || Project.new(id: 0, user_id: 0, name: 'pm_mm', identifier: 'pm_mm', is_public:true)
+ if issue.pm_project_id.present?
+ Api::Pm::Issues::UpdateService.call(issue_project, issue, {status_id: status_id}, user, "Project##{project.id}@#{commitsha}")
+ else
+ Api::V1::Issues::UpdateService.call(issue_project, issue, {status_id: status_id}, user, "Project##{project.id}@#{commitsha}")
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/jobs/open_project_dev_ops_job.rb b/app/jobs/open_project_dev_ops_job.rb
index b3fe99e6f..c5d68a861 100644
--- a/app/jobs/open_project_dev_ops_job.rb
+++ b/app/jobs/open_project_dev_ops_job.rb
@@ -6,6 +6,7 @@ class OpenProjectDevOpsJob < ApplicationJob
def perform(project_id, user_id)
project = Project.find_by(id: project_id)
return if project.blank?
+ return if EduSetting.get("jianmu_devops_url").blank?
user = User.find_by(id: user_id)
code = jianmu_devops_code(project, user)
uri = URI.parse("#{jianmu_devops_url}/activate?code=#{URI.encode_www_form_component(code)}")
diff --git a/app/jobs/update_project_topic_job.rb b/app/jobs/update_project_topic_job.rb
index 52341d672..b3ef540aa 100644
--- a/app/jobs/update_project_topic_job.rb
+++ b/app/jobs/update_project_topic_job.rb
@@ -4,6 +4,7 @@ class UpdateProjectTopicJob < ApplicationJob
queue_as :message
def perform(project_id)
+ return unless $redis_cache.set("UpdateProjectTopicJob:#{project_id}", 1, nx: true, ex: 10.seconds)
project = Project.find_by(id: project_id)
return if project.blank?
begin
@@ -25,7 +26,9 @@ class UpdateProjectTopicJob < ApplicationJob
topic_count +=1
end
end
+ $redis_cache.del("UpdateProjectTopicJob:#{project_id}")
rescue => e
+ $redis_cache.del("UpdateProjectTopicJob:#{project_id}")
puts "get_repos_languages: error:#{e.message}"
end
end
diff --git a/app/libs/ci4s/service.rb b/app/libs/ci4s/service.rb
new file mode 100644
index 000000000..ebef5a4c5
--- /dev/null
+++ b/app/libs/ci4s/service.rb
@@ -0,0 +1,70 @@
+require 'oauth2'
+
+module Ci4s::Service
+ module_function
+
+ def client_id
+ config = Rails.application.config_for(:configuration)
+ config.dig("oauth", "ci4s", "client_id")
+ end
+
+ def client_secret
+ config = Rails.application.config_for(:configuration)
+ config.dig("oauth", "ci4s", "client_secret")
+ end
+
+ def base_url
+ config = Rails.application.config_for(:configuration)
+ config.dig("oauth", "ci4s", "base_url")
+ end
+
+ def redirect_uri
+ config = Rails.application.config_for(:configuration)
+ config.dig("oauth", "ci4s", "redirect_uri")
+ end
+
+ def oauth_url
+ "#{base_url}/oauth/authorize?client_id=#{client_id}&redirect_uri=#{URI.encode_www_form_component(redirect_uri)}&response_type=code&grant_type=authorization_code"
+ end
+
+ def request(method, url, params)
+ begin
+ Rails.logger.info("[Ci4sOauth] [#{method.to_s.upcase}] #{url} || #{params}")
+
+ client ||= begin
+ Faraday.new(url: base_url) do |req|
+ req.request :url_encoded
+ req.headers['Content-Type'] = 'application/json'
+ req.response :logger # 显示日志
+ req.adapter Faraday.default_adapter
+ req.authorization :Bearer, params[:access_token]
+ req.headers['Authorization']
+ end
+ end
+ response = client.public_send(method, url, params)
+ result = JSON.parse(response.body)
+
+ Rails.logger.info("[Ci4sOauth] [#{response.status}] #{result}")
+
+ result
+ rescue Exception => e
+ raise Gitlink::TipException.new(e.message)
+ end
+ end
+
+ def access_token(code)
+ begin
+ Rails.logger.info("[Ci4sOauth] [code] #{code} ")
+ Rails.logger.info("[Ci4sOauth] [redirect_uri] #{redirect_uri} ")
+ client = OAuth2::Client.new(client_id, client_secret, site: base_url)
+ result = client.auth_code.get_token(code, redirect_uri: redirect_uri).to_hash
+ return result
+ rescue Exception => e
+ raise Gitlink::TipException.new(e.message)
+ end
+ end
+
+ def user_info(access_token)
+ request(:get, '/user/info', {access_token: access_token})
+ end
+end
\ No newline at end of file
diff --git a/app/libs/educoder_oauth/service.rb b/app/libs/educoder_oauth/service.rb
index 1dcb2cfdd..c53ca34ce 100644
--- a/app/libs/educoder_oauth/service.rb
+++ b/app/libs/educoder_oauth/service.rb
@@ -7,7 +7,7 @@ module EducoderOauth::Service
begin
Rails.logger.info("[EducoderOauth] [#{method.to_s.upcase}] #{url} || #{params}")
- client = Faraday.new(url: EducoderOauth.base_url)
+ client = Faraday.new(url: EducoderOauth.base_url, headers: {'X-EDU-Timestamp' => "#{Time.now.to_i}"})
response = client.public_send(method, url, params)
result = JSON.parse(response.body)
diff --git a/app/models/action/node.rb b/app/models/action/node.rb
index 69e45b3a8..0dce4cd68 100644
--- a/app/models/action/node.rb
+++ b/app/models/action/node.rb
@@ -16,9 +16,14 @@
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
+# label :string(255)
+# node_type :string(255)
+# is_mutil_link :boolean
+# link_type :string(255)
#
# Indexes
#
+# by_name (name)
# index_action_nodes_on_action_types_id (action_node_types_id)
# index_action_nodes_on_user_id (user_id)
#
@@ -33,39 +38,37 @@ class Action::Node < ApplicationRecord
belongs_to :user, optional: true
+ attr_accessor :cust_name, :run_values, :input_values, :parent_node_id, :sub_nodes, :link_type_array, :node_id
- # def content_yaml
- # "foo".to_yaml
- # <<~YAML
- # - name: Set up JDK ${{ matrix.java }}
- # uses: actions/setup-java@v3
- # with:
- # distribution: 'temurin'
- # java-version: ${{ matrix.java }}
- # YAML
- # end
+ validates :name, presence: { message: "不能为空" }
+ validates :full_name, length: { maximum: 200, too_long: "不能超过200个字符" }
+ validates :label, length: { maximum: 200, too_long: "不能超过200个字符" }
+ validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
- def yaml_hash
+
+
+ def content_yaml
+ "foo".to_yaml
<<~YAML
- name: Check dist
-
- on:
- push:
- branches:
- - main
- paths-ignore:
- - '**.md'
- pull_request:
- paths-ignore:
- - '**.md'
- workflow_dispatch:
-
- jobs:
- call-check-dist:
- name: Check dist/
- uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main
- with:
- node-version: '20.x'
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.java }}
YAML
end
+
+ def node
+ self
+ end
+
+ def build_yaml
+ yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_node.yaml.erb"))).result(binding)
+ # 删除空行内容
+ yaml = yaml.gsub(/^\s*\n/, "")
+ # Rails.logger.info "========================="
+ # Rails.logger.info yaml
+ yaml
+ end
+
end
diff --git a/app/models/action/node_input.rb b/app/models/action/node_input.rb
index 4f3825170..3710e1633 100644
--- a/app/models/action/node_input.rb
+++ b/app/models/action/node_input.rb
@@ -8,7 +8,7 @@
# input_type :string(255)
# description :string(255)
# is_required :boolean default("0")
-# sort_no :string(255) default("0")
+# sort_no :integer default("0")
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
@@ -24,4 +24,7 @@ class Action::NodeInput < ApplicationRecord
default_scope { order(sort_no: :asc) }
belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
+
+ validates :name, presence: { message: "不能为空" }
+ validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
end
diff --git a/app/models/action/node_select.rb b/app/models/action/node_select.rb
index 25be51f99..23cd87bb8 100644
--- a/app/models/action/node_select.rb
+++ b/app/models/action/node_select.rb
@@ -29,6 +29,9 @@ class Action::NodeSelect < ApplicationRecord
belongs_to :action_node, :class_name => 'Action::Node', foreign_key: "action_nodes_id"
belongs_to :user, optional: true
+ validates :name, presence: { message: "不能为空" }
+ validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
+
def value
if self.val_ext.blank?
self.val
diff --git a/app/models/action/node_type.rb b/app/models/action/node_type.rb
index 7ce78b0fb..64d9eabce 100644
--- a/app/models/action/node_type.rb
+++ b/app/models/action/node_type.rb
@@ -5,7 +5,7 @@
# id :integer not null, primary key
# name :string(255)
# description :string(255)
-# sort_no :integer
+# sort_no :integer default("0")
# created_at :datetime not null
# updated_at :datetime not null
#
@@ -15,4 +15,7 @@ class Action::NodeType < ApplicationRecord
default_scope { order(sort_no: :asc) }
has_many :action_nodes, :class_name => 'Action::Node', foreign_key: "action_node_types_id"
+
+ validates :name, presence: { message: "不能为空" }
+ validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
end
diff --git a/app/models/action/pipeline.rb b/app/models/action/pipeline.rb
new file mode 100644
index 000000000..227fa4aa8
--- /dev/null
+++ b/app/models/action/pipeline.rb
@@ -0,0 +1,39 @@
+# == Schema Information
+#
+# Table name: action_pipelines
+#
+# id :integer not null, primary key
+# project_id :integer
+# user_id :integer
+# pipeline_name :string(255)
+# pipeline_status :string(255)
+# description :string(255)
+# file_name :string(255)
+# is_graphic_design :boolean default("0")
+# repo_name :string(255)
+# repo_identifier :string(255)
+# repo_owner :string(255)
+# branch :string(255)
+# event :string(255)
+# sha :string(255)
+# json :text(65535)
+# yaml :text(65535)
+# disable :boolean default("0")
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_action_pipelines_on_project_id (project_id)
+# index_action_pipelines_on_user_id (user_id)
+#
+
+class Action::Pipeline < ApplicationRecord
+ self.table_name = 'action_pipelines'
+ belongs_to :user, optional: true
+ belongs_to :project
+
+ validates :name, presence: { message: "不能为空" }
+ validates :json, length: { maximum: 65535, too_long: "不能超过65535个字符"}
+ validates :yaml, length: { maximum: 65535, too_long: "不能超过65535个字符"}
+end
diff --git a/app/models/action/pipeline_result.rb b/app/models/action/pipeline_result.rb
new file mode 100644
index 000000000..e4edc024b
--- /dev/null
+++ b/app/models/action/pipeline_result.rb
@@ -0,0 +1,25 @@
+# == Schema Information
+#
+# Table name: action_pipeline_results
+#
+# id :integer not null, primary key
+# project_id :integer
+# run_id :integer
+# step_id :string(255)
+# job_name :string(255)
+# job_show_type :string(255)
+# job_result :text(65535)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_action_pipeline_results_on_project_id (project_id)
+# index_action_pipeline_results_on_run_id (run_id)
+#
+
+class Action::PipelineResult < ApplicationRecord
+ self.table_name = 'action_pipeline_results'
+ belongs_to :project
+end
+
diff --git a/app/models/action/template.rb b/app/models/action/template.rb
index 34b669f66..d256c7f67 100644
--- a/app/models/action/template.rb
+++ b/app/models/action/template.rb
@@ -6,7 +6,7 @@
# name :string(255)
# description :string(255)
# img :string(255)
-# sort_no :string(255) default("0")
+# sort_no :integer default("0")
# json :text(65535)
# yaml :text(65535)
# created_at :datetime not null
@@ -17,4 +17,7 @@ class Action::Template < ApplicationRecord
self.table_name = 'action_templates'
default_scope { order(sort_no: :asc) }
+ validates :name, presence: { message: "不能为空" }
+ validates :description, length: { maximum: 65535, too_long: "不能超过65535个字符"}
+
end
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
index a7d874805..330979e82 100644
--- a/app/models/attachment.rb
+++ b/app/models/attachment.rb
@@ -26,8 +26,6 @@
# cloud_url :string(255) default("")
# course_second_category_id :integer default("0")
# delay_publish :boolean default("0")
-# memo_image :boolean default("0")
-# extra_type :integer default("0")
# uuid :string(255)
#
# Indexes
@@ -38,12 +36,14 @@
# index_attachments_on_created_on (created_on)
# index_attachments_on_is_public (is_public)
# index_attachments_on_quotes (quotes)
+# index_attachments_on_uuid (uuid)
#
+
class Attachment < ApplicationRecord
include BaseModel
include Publicable
@@ -76,6 +76,42 @@ class Attachment < ApplicationRecord
DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
+ def self.build_from_remote_url(user, name, url, container=nil)
+ ext = name.split('.')[-1]
+ tmp_path = "#{Rails.root}/#{name}"
+ uri = URI(url)
+ size = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
+ response = http.get(uri.path)
+ File.open(tmp_path, 'wb') do |file|
+ file.write(response.body)
+ end
+ end
+ digest = "#{Digest::MD5.file(tmp_path).hexdigest}_#{(Time.now.to_f * 1000).to_i}.#{ext}"
+ month_folder = "#{Time.now.year}/#{Time.now.month.to_s.rjust(2, '0')}"
+ save_path = "#{Rails.root}#{EduSetting.get("attachment_folder")}#{month_folder}"
+ unless Dir.exists?(save_path)
+ FileUtils.mkdir_p(save_path) ##不成功这里会抛异常
+ end
+ path = File.join(save_path, digest)
+ FileUtils.mv(tmp_path, path)
+ attachment = Attachment.new
+ attachment.filename = name
+ attachment.disk_filename = path[save_path.size+1, path.size]
+ attachment.filesize = size
+ attachment.content_type = 'application/octet-stream'
+ attachment.digest = digest.split('.')[0]
+ attachment.author_id = user.id
+ attachment.disk_directory = month_folder
+ attachment.cloud_url = url
+ attachment.uuid = SecureRandom.uuid
+ attachment.container = container
+ attachment.save!
+
+ return attachment
+ rescue
+ return nil
+ end
+
def diskfile
File.join(File.join(Rails.root, "files"), disk_directory.to_s, disk_filename.to_s)
end
diff --git a/app/models/concerns/matchable.rb b/app/models/concerns/matchable.rb
index 27e5a0dda..3ada903af 100644
--- a/app/models/concerns/matchable.rb
+++ b/app/models/concerns/matchable.rb
@@ -6,8 +6,8 @@ module Matchable
scope :with_project_language, ->(language_id) { where(project_language_id: language_id) unless language_id.blank? }
scope :with_project_type, ->(project_type) { where(project_type: project_type) if Project.project_types.include?(project_type) }
scope :by_name_or_identifier, ->(search) { where("name like :search or identifier LIKE :search", :search => "%#{search.split(" ").join('|')}%") unless search.blank? }
- scope :with_project_topic, ->(topic_id) {joins(:project_topics).where(project_topics: {id: topic_id}) unless topic_id.blank?}
- scope :with_project_topic_name, ->(topic_name) {joins(:project_topics).where(project_topics: {name: topic_name}) unless topic_name.blank?}
+ scope :with_project_topic, ->(topic_id) {left_outer_joins(:project_topics).where(project_topics: {id: topic_id}) unless topic_id.blank?}
+ scope :with_project_topic_name, ->(topic_name) {left_outer_joins(:project_topics).where(project_topics: {name: topic_name}) unless topic_name.blank?}
end
end
diff --git a/app/models/edu_setting.rb b/app/models/edu_setting.rb
index f4a89c09b..708aa625e 100644
--- a/app/models/edu_setting.rb
+++ b/app/models/edu_setting.rb
@@ -1,22 +1,23 @@
-# == Schema Information
-#
-# Table name: edu_settings
-#
-# id :integer not null, primary key
-# name :string(255)
-# value :string(255)
-# created_at :datetime not null
-# updated_at :datetime not null
-# description :string(255)
-#
-# Indexes
-#
-# index_edu_settings_on_name (name) UNIQUE
-#
-
+# == Schema Information
+#
+# Table name: edu_settings
+#
+# id :integer not null, primary key
+# name :string(255)
+# value :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+# description :string(255)
+#
+# Indexes
+#
+# index_edu_settings_on_name (name) UNIQUE
+#
+
class EduSetting < ApplicationRecord
- after_commit :expire_value_cache
+ after_commit :expire_value_cache, on: [:create, :update]
+ after_commit :clear_value_cache, on: :destroy
scope :by_search, -> (keyword){ where("name LIKE :keyword OR value LIKE :keyword", keyword: "%#{strip_param(keyword)}%") unless strip_param(keyword).blank? }
@@ -25,7 +26,11 @@ class EduSetting < ApplicationRecord
end
def self.get(key)
- Rails.cache.fetch(value_cache_key(key), expires_in: 1.days) do
+ begin
+ Rails.cache.fetch(value_cache_key(key), expires_in: 1.days) do
+ find_by_name(key.to_s)&.value
+ end
+ rescue Exception => e
find_by_name(key.to_s)&.value
end
end
@@ -41,4 +46,8 @@ class EduSetting < ApplicationRecord
def expire_value_cache
Rails.cache.write(value_cache_key, value)
end
+
+ def clear_value_cache
+ Rails.cache.delete(value_cache_key)
+ end
end
diff --git a/app/models/gitea/action_run.rb b/app/models/gitea/action_run.rb
new file mode 100644
index 000000000..b6c8693fc
--- /dev/null
+++ b/app/models/gitea/action_run.rb
@@ -0,0 +1,9 @@
+class Gitea::ActionRun < Gitea::Base
+ self.inheritance_column = nil # FIX The single-table inheritance mechanism failed
+ # establish_connection :gitea_db
+
+ self.table_name = "action_run"
+
+ # belongs_to :user, class_name: '::User', primary_key: :gitea_uid, foreign_key: :owner_id, optional: true
+
+end
diff --git a/app/models/gitlink_competition_apply.rb b/app/models/gitlink_competition_apply.rb
index f3b7d4ce1..cc942bd45 100644
--- a/app/models/gitlink_competition_apply.rb
+++ b/app/models/gitlink_competition_apply.rb
@@ -1,3 +1,23 @@
+# == Schema Information
+#
+# Table name: gitlink_competition_applies
+#
+# id :integer not null, primary key
+# competition_id :integer
+# competition_identifier :string(255)
+# team_id :integer
+# team_name :string(255)
+# school_name :string(255)
+# educoder_login :string(255)
+# nickname :string(255)
+# phone :string(255)
+# email :string(255)
+# identity :string(255)
+# role :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+
# == Schema Information
#
# Table name: gitlink_competition_applies
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 86da50825..d1f164d6c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -63,16 +63,17 @@ class Issue < ApplicationRecord
has_many :project_trends, as: :trend, dependent: :destroy
has_one :pull_request
# belongs_to :issue_tag,optional: true
- belongs_to :priority, :class_name => 'IssuePriority', foreign_key: :priority_id,optional: true
+ belongs_to :priority, class_name: 'IssuePriority', foreign_key: :priority_id,optional: true
belongs_to :version, foreign_key: :fixed_version_id,optional: true, counter_cache: true
belongs_to :user,optional: true, foreign_key: :author_id
belongs_to :issue_status, foreign_key: :status_id,optional: true
+ belongs_to :parent_issue, class_name: 'Issue', optional: true, foreign_key: :root_id, counter_cache: :child_count
has_many :commit_issues
has_many :attachments, as: :container, dependent: :destroy
# has_many :memos
- has_many :journals, :as => :journalized, :dependent => :destroy
+ has_many :journals, as: :journalized, dependent: :destroy
has_many :journal_details, through: :journals
- has_many :claims, :dependent => :destroy
+ has_many :claims, dependent: :destroy
has_many :claim_users, through: :claims, source: :user
has_many :issue_tags_relates, dependent: :destroy
has_many :issue_tags, through: :issue_tags_relates
@@ -82,25 +83,68 @@ class Issue < ApplicationRecord
has_many :assigners, through: :issue_assigners
has_many :issue_participants, dependent: :destroy
has_many :participants, through: :issue_participants
- has_many :show_participants, -> {joins(:issue_participants).where.not(issue_participants: {participant_type: "atme"}).distinct}, through: :issue_participants, source: :participant
+ has_many :children_issues, class_name: 'Issue', foreign_key: :root_id, dependent: :destroy
+ has_many :show_participants, -> {joins(:issue_participants).where.not(issue_participants: {participant_type: 'atme'}).distinct}, through: :issue_participants, source: :participant
has_many :show_assigners, -> {joins(:issue_assigners).distinct}, through: :issue_assigners, source: :assigner
has_many :show_issue_tags, -> {joins(:issue_tags_relates).distinct}, through: :issue_tags_relates, source: :issue_tag
- has_many :comment_journals, -> {where.not(notes: nil)}, class_name: "Journal", :as => :journalized
- has_many :operate_journals, -> {where(notes: nil)}, class_name: "Journal", :as => :journalized
- has_many :pull_attached_issues, dependent: :destroy
+ has_many :comment_journals, -> {where.not(notes: nil)}, class_name: 'Journal', as: :journalized
+ has_many :operate_journals, -> {where(notes: nil)}, class_name: 'Journal', as: :journalized
+ has_many :pull_attached_issues, dependent: :destroy
has_many :attach_pull_requests, through: :pull_attached_issues, source: :pull_request
+ # PM 关联工作项目
+ has_many :pm_links, as: :linkable, dependent: :destroy
+
+ belongs_to :changer, class_name: 'User', foreign_key: :changer_id, optional: true
scope :issue_includes, ->{includes(:user)}
scope :issue_many_includes, ->{includes(journals: :user)}
- scope :issue_issue, ->{where(issue_classify: [nil,"issue"])}
- scope :issue_pull_request, ->{where(issue_classify: "pull_request")}
+ scope :issue_issue, ->{where(issue_classify: [nil, 'issue'])}
+ scope :issue_pull_request, ->{where(issue_classify: 'pull_request')}
scope :issue_index_includes, ->{includes(:tracker, :priority, :version, :issue_status, :journals,:issue_tags,user: :user_extension)}
+ scope :pm_includes, -> {includes(:project, :show_issue_tags, :issue_status, :priority, :version, :user, :show_assigners, :comment_journals, :operate_journals)}
scope :closed, ->{where(status_id: 5)}
scope :opened, ->{where.not(status_id: 5)}
after_create :incre_project_common, :incre_user_statistic, :incre_platform_statistic
- after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container
- after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic
+ before_save :check_pm_and_update_due_date
+ after_save :incre_or_decre_closed_issues_count, :change_versions_count, :send_update_message_to_notice_system, :associate_attachment_container, :generate_uuid
+ after_destroy :update_closed_issues_count_in_project!, :decre_project_common, :decre_user_statistic, :decre_platform_statistic, :destroy_be_pm_links
+
+
+ def pm_issue_type_string
+ case pm_issue_type
+ when 1
+ "requirement"
+ when 2
+ "task"
+ when 3
+ "bug"
+ else
+ "issue"
+ end
+ end
+
+ def destroy_be_pm_links
+ PmLink.where(be_linkable_type:"Issue",be_linkable_id:self.id).map(&:destroy)
+ end
+
+ def check_pm_and_update_due_date
+ if pm_project_id.present? && pm_issue_type.present? && status_id_changed?
+ status_ids = case pm_issue_type
+ when 1
+ [3,5]
+ when 2
+ [3,5]
+ when 3
+ [5]
+ else
+ []
+ end
+ if status_ids.include? self.status_id
+ self.due_date = self.due_date || Time.current
+ end
+ end
+ end
def is_issuely_issue?
self.issue_classify.nil? || self.issue_classify == 'issue'
@@ -118,27 +162,34 @@ class Issue < ApplicationRecord
end
def incre_project_common
- CacheAsyncSetJob.perform_later("project_common_service", {issues: 1}, self.project_id) if is_issuely_issue?
+ CacheAsyncSetJob.perform_later('project_common_service', {issues: 1}, self.project_id) if is_issuely_issue?
end
def decre_project_common
- CacheAsyncSetJob.perform_later("project_common_service", {issues: -1}, self.project_id) if is_issuely_issue?
+ CacheAsyncSetJob.perform_later('project_common_service', {issues: -1}, self.project_id) if is_issuely_issue?
end
def incre_user_statistic
- CacheAsyncSetJob.perform_later("user_statistic_service", {issue_count: 1}, self.author_id) if is_issuely_issue?
+ CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: 1}, self.author_id) if is_issuely_issue?
end
def decre_user_statistic
- CacheAsyncSetJob.perform_later("user_statistic_service", {issue_count: -1}, self.author_id) if is_issuely_issue?
+ CacheAsyncSetJob.perform_later('user_statistic_service', {issue_count: -1}, self.author_id) if is_issuely_issue?
+ end
+
+ def refresh_root_issue_count
+ return if root_id.nil? || root_id.zero?
+ root_issue = Issue.find_by(id: root_id)
+ root_count = Issue.where(root_id: root_id).count
+ root_issue.update(child_count: root_count)
end
def incre_platform_statistic
- CacheAsyncSetJob.perform_later("platform_statistic_service", {issue_count: 1}) if is_issuely_issue?
+ CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: 1}) if is_issuely_issue?
end
def decre_platform_statistic
- CacheAsyncSetJob.perform_later("platform_statistic_service", {issue_count: -1}) if is_issuely_issue?
+ CacheAsyncSetJob.perform_later('platform_statistic_service', {issue_count: -1}) if is_issuely_issue?
end
def get_assign_user
@@ -147,20 +198,20 @@ class Issue < ApplicationRecord
def create_journal_detail(change_files, issue_files, issue_file_ids, user_id)
journal_params = {
- journalized_id: self.id, journalized_type: "Issue", user_id: user_id
+ journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
}
journal = Journal.new journal_params
if journal.save
if change_files
- old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(",")
- new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(",")
- journal.journal_details.create(property: "attachment", prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
+ old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(',')
+ new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(',')
+ journal.journal_details.create(property: 'attachment', prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
end
change_values = %w(subject description is_private assigned_to_id tracker_id status_id priority_id fixed_version_id start_date due_date estimated_hours done_ratio issue_tags_value issue_type token branch_name)
change_values.each do |at|
if self.send("saved_change_to_#{at}?")
- journal.journal_details.create(property: "attr", prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
+ journal.journal_details.create(property: 'attr', prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
end
end
end
@@ -168,11 +219,11 @@ class Issue < ApplicationRecord
def custom_journal_detail(prop_key, old_value, value, user_id)
journal_params = {
- journalized_id: self.id, journalized_type: "Issue", user_id: user_id
+ journalized_id: self.id, journalized_type: 'Issue', user_id: user_id
}
journal = Journal.new journal_params
if journal.save
- journal.journal_details.create(property: "attr", prop_key: prop_key, old_value: old_value, value: value)
+ journal.journal_details.create(property: 'attr', prop_key: prop_key, old_value: old_value, value: value)
end
end
@@ -192,20 +243,29 @@ class Issue < ApplicationRecord
end
end
+ def generate_uuid
+ # return if pm_project_id.nil?
+ # attachments.map(&:generate_uuid)
+ end
+
def is_collaborators?
- self.assigned_to_id.present? ? self.project.member?(self.assigned_to_id) : false
+ if self.assigned_to_id.present? && self.project.present?
+ self.project.member?(self.assigned_to_id)
+ else
+ false
+ end
end
def get_issue_tags_name
if issue_tags.present?
- issue_tags.select(:name).uniq.pluck(:name).join(",")
+ issue_tags.select(:name).uniq.pluck(:name).join(',')
else
nil
end
end
def only_reply_journals
- journals.where.not(notes: [nil, ""]).journal_includes.limit(2)
+ journals.where.not(notes: [nil, '']).journal_includes.limit(2)
end
def change_versions_count
@@ -235,7 +295,7 @@ class Issue < ApplicationRecord
end
def update_closed_issues_count_in_project!
- self.project.decrement!(:closed_issues_count) if self.status_id == 5
+ self.project.decrement!(:closed_issues_count) if self.status_id == 5 && self.project.present?
end
def send_update_message_to_notice_system
@@ -267,8 +327,8 @@ class Issue < ApplicationRecord
def to_builder
Jbuilder.new do |issue|
issue.(self, :id, :project_issues_index, :subject, :description, :branch_name, :start_date, :due_date)
- issue.created_at self.created_on.strftime("%Y-%m-%d %H:%M")
- issue.updated_at self.updated_on.strftime("%Y-%m-%d %H:%M")
+ issue.created_at self.created_on.strftime('%Y-%m-%d %H:%M')
+ issue.updated_at self.updated_on.strftime('%Y-%m-%d %H:%M')
issue.tags self.show_issue_tags.map{|t| JSON.parse(t.to_builder.target!)}
issue.status self.issue_status.to_builder
if self.priority.present?
@@ -290,4 +350,12 @@ class Issue < ApplicationRecord
end
end
+ def self.full_children_issues(issue, issues = [])
+ issue.children_issues.each do |i|
+ issues << i
+ full_children_issues(i, issues)
+ end
+ issues
+ end
+
end
diff --git a/app/models/issue_priority.rb b/app/models/issue_priority.rb
index 5bf70da05..5904af649 100644
--- a/app/models/issue_priority.rb
+++ b/app/models/issue_priority.rb
@@ -38,4 +38,21 @@ class IssuePriority < ApplicationRecord
priority.(self, :id, :name)
end
end
+
+ def pm_color
+ case name
+ when '低'
+ '#13b33e'
+ when '正常'
+ '#0d5ef8'
+ when '高'
+ '#ff6f00'
+ when '紧急'
+ '#d20f0f'
+ # when '立刻'
+ # '#f5222d'
+ else
+ '#13b33e'
+ end
+ end
end
diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb
index fde871182..63c15bd89 100644
--- a/app/models/issue_status.rb
+++ b/app/models/issue_status.rb
@@ -45,9 +45,28 @@ class IssueStatus < ApplicationRecord
end
end
- def to_builder
+ def to_builder
Jbuilder.new do |status|
status.(self, :id, :name)
end
end
+
+ def pm_color
+ case name
+ when '新增'
+ '#ff6f00'
+ when '正在解决'
+ '#0d5ef8'
+ when '已解决'
+ '#13b33e'
+ when '关闭'
+ '#b1aaa5'
+ # when '反馈'
+ # '#13c2c2'
+ when '拒绝'
+ '#ff0000'
+ else
+ '#ff6f00'
+ end
+ end
end
diff --git a/app/models/issue_tag.rb b/app/models/issue_tag.rb
index f5f0abbd8..496cc907e 100644
--- a/app/models/issue_tag.rb
+++ b/app/models/issue_tag.rb
@@ -15,9 +15,11 @@
# gitea_url :string(255)
# pull_requests_count :integer default("0")
# pm_project_id :integer
+# organization_id :integer
#
# Indexes
#
+# index_issue_tags_on_organization_id (organization_id)
# index_issue_tags_on_user_id_and_name_and_project_id (user_id,name,project_id)
#
@@ -29,8 +31,15 @@ class IssueTag < ApplicationRecord
has_many :pull_request_issues, -> {where(issue_classify: "pull_request")}, source: :issue, through: :issue_tags_relates
belongs_to :project, optional: true, counter_cache: true
belongs_to :user, optional: true
+ belongs_to :organization, optional: true
- validates :name, uniqueness: {scope: :project_id, message: "已存在" }
+ scope :pm_able, -> {where(project_id: 0)}
+
+ validates :name, uniqueness: {scope: :project_id, message: "已存在" }, if: :pm_project?
+
+ def pm_project?
+ !project_id.zero?
+ end
def self.init_data(project_id)
data = init_issue_tag_data
@@ -41,6 +50,24 @@ class IssueTag < ApplicationRecord
$redis_cache.hset("project_init_issue_tags", project_id, 1)
end
+ def self.pm_init_data(pm_project_id)
+ data = init_issue_tag_data
+ data.each do |item|
+ next if IssueTag.exists?(pm_project_id: pm_project_id, project_id: 0, name: item[0])
+ IssueTag.create!(pm_project_id: pm_project_id, project_id: 0, name: item[0], description: item[1], color: item[2])
+ end
+ $redis_cache.hset("pm_project_init_issue_tags", pm_project_id, 1)
+ end
+
+ def self.pm_org_init_data(organization_id)
+ data = init_issue_tag_data
+ data.each do |item|
+ next if IssueTag.exists?(organization_id: organization_id, project_id: 0, name: item[0])
+ IssueTag.create!(organization_id: organization_id, project_id: 0, name: item[0], description: item[1], color: item[2])
+ end
+ $redis_cache.hset("pm_org_init_issue_tags", organization_id, 1)
+ end
+
def reset_counter_field
self.update_column(:issues_count, issue_issues.size)
self.update_column(:pull_requests_count, pull_request_issues.size)
diff --git a/app/models/journal.rb b/app/models/journal.rb
index 4ee37714b..196a76165 100644
--- a/app/models/journal.rb
+++ b/app/models/journal.rb
@@ -22,6 +22,7 @@
# resolveer_id :integer
# need_respond :boolean default("0")
# updated_on :datetime
+# operate_by :string(255) default("Issue")
#
# Indexes
#
@@ -51,6 +52,7 @@ class Journal < ApplicationRecord
scope :journal_includes, ->{includes(:user, :journal_details, :attachments)}
scope :parent_journals, ->{where(parent_id: nil)}
scope :children_journals, lambda{|journal_id| where(parent_id: journal_id)}
+ scope :operate_journals, ->{where(notes: nil)}
enum state: {opened: 0, resolved: 1, disabled: 2}
@@ -82,15 +84,460 @@ class Journal < ApplicationRecord
end
end
- def operate_content
- content = ""
+ def operate_by_content
+ return '' if self.operate_by == "Issue"
+ if self.operate_by.starts_with?("Project#")
+ project_id, commit_sha = self.operate_by.scan(/#(\d+).*?@(\w+)/)[0]
+ project =Project.find_by_id(project_id)
+ return "通过 #{project&.owner&.real_name}/#{project&.name} 提交 #{commit_sha[0...10]}"
+ end
+ rescue
+ return ''
+ end
+
+ def pm_operate_category
+ detail = self.journal_details.take
+ if %w(requirement task bug).include?(detail.property) && detail.prop_key.to_s == "1"
+ return "issue"
+ else
+ return %w(requirement task bug attr).include?(detail.property) ? detail.prop_key : detail.property
+ end
+ end
+
+ def pm_dashboard_operate_content
+ content = self.pm_operate_content
+ if content.start_with?('创建了')
+ content += "#{self.issue.subject}"
+ else
+ prefix = '将'
+ prefix += "计划" if self.issue.pm_issue_type == 1
+ prefix += "任务" if self.issue.pm_issue_type == 2
+ prefix += "缺陷" if self.issue.pm_issue_type == 3
+ prefix += "#{self.issue.subject}"
+ content = prefix + content.sub('将', '的')
+ end
+ content
+ end
+
+ def pm_operate_content
+ content = "#{operate_by_content}"
+ detail = self.journal_details.take
+ case detail.property
+ when 'requirement'
+ case detail.prop_key
+ when 'status_id'
+ old_value = IssueStatus.find_by_id(detail.old_value)&.name
+ new_value = IssueStatus.find_by_id(detail.value)&.name
+ content += "将状态"
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "设置为#{new_value}"
+ else
+ new_value = "未设置" if detail.value.blank?
+ content += "由#{old_value}更改为#{new_value}"
+ end
+ content.gsub!('新增', '待评审')
+ content.gsub!('正在解决', '进行中')
+ content.gsub!('已解决', '已完成')
+ content.gsub!('关闭', '已关闭')
+ content.gsub!('拒绝', '已拒绝')
+ return content
+ when 'root_id'
+ old_value = "<#{Issue.find_by_id(detail.old_value)&.subject}>"
+ new_value = "<#{Issue.find_by_id(detail.value)&.subject}>"
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了父需求#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的父需求#{old_value}"
+ else
+ content += "将关联的父需求由#{old_value}更改为#{new_value}"
+ end
+ end
+ return content
+ when 'leaf_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "新建了子需求#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "删除了关联的子需求#{old_value}"
+ else
+ content += "新建了子需求#{new_value}"
+ end
+ end
+ return content
+ when 'tag_leaf_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了子需求#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的子需求#{old_value}"
+ else
+ content += "关联了子需求#{new_value}"
+ end
+ end
+ return content
+ else
+ content += "创建了需求"
+ end
+ when 'task'
+ case detail.prop_key
+ when 'status_id'
+ old_value = IssueStatus.find_by_id(detail.old_value)&.name
+ new_value = IssueStatus.find_by_id(detail.value)&.name
+ content += "将状态"
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "设置为#{new_value}"
+ else
+ new_value = "未设置" if detail.value.blank?
+ content += "由#{old_value}更改为#{new_value}"
+ end
+ content.gsub!('新增', '待处理')
+ content.gsub!('正在解决', '进行中')
+ content.gsub!('已解决', '已完成')
+ content.gsub!('关闭', '已关闭')
+ content.gsub!('拒绝', '已拒绝')
+ return content
+ when 'root_id'
+ old_value = "<#{Issue.find_by_id(detail.old_value)&.subject}>"
+ new_value = "<#{Issue.find_by_id(detail.value)&.subject}>"
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了父任务#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的父任务#{old_value}"
+ else
+ content += "将关联的父任务由#{old_value}>更改为#{new_value}"
+ end
+ end
+ return content
+ when 'leaf_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "新建了子任务#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "删除了关联的子任务#{old_value}"
+ else
+ content += "新建了子任务#{new_value}"
+ end
+ end
+ return content
+ when 'tag_leaf_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了子任务#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的子任务#{old_value}"
+ else
+ content += "关联了子任务#{new_value}"
+ end
+ end
+ return content
+ else
+ content += "创建了任务"
+ end
+ when 'bug'
+ case detail.prop_key
+ when 'status_id'
+ old_value = IssueStatus.find_by_id(detail.old_value)&.name
+ new_value = IssueStatus.find_by_id(detail.value)&.name
+ content += "将状态"
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "设置为#{new_value}"
+ else
+ new_value = "未设置" if detail.value.blank?
+ content += "由#{old_value}更改为#{new_value}"
+ end
+ content.gsub!('新增', '待修复')
+ content.gsub!('正在解决', '修复中')
+ content.gsub!('已解决', '已修复')
+ content.gsub!('关闭', '已关闭')
+ content.gsub!('拒绝', '已拒绝')
+ return content
+ when 'root_id'
+ old_value = "<#{Issue.find_by_id(detail.old_value)&.subject}>"
+ new_value = "<#{Issue.find_by_id(detail.value)&.subject}>"
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了父缺陷#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的父缺陷#{old_value}"
+ else
+ content += "将关联的父缺陷由#{old_value}更改为#{new_value}"
+ end
+ end
+ return content
+ when 'leaf_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "新建了子缺陷#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "删除了关联的子缺陷#{old_value}"
+ else
+ content += "新建了子缺陷#{new_value}"
+ end
+ end
+ return content
+ when 'tag_leaf_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了子缺陷#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的子缺陷#{old_value}"
+ else
+ content += "关联了子缺陷#{new_value}"
+ end
+ end
+ return content
+ else
+ content += "创建了缺陷"
+ end
+ when 'attr'
+ case detail.prop_key
+ when 'subject'
+ content += "修改了标题"
+ when 'description'
+ content += "修改了正文"
+ when 'priority_id'
+ old_value = IssuePriority.find_by_id(detail.old_value)&.name
+ new_value = IssuePriority.find_by_id(detail.value)&.name
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "将优先级设置为#{new_value}"
+ else
+ new_value = "未设置" if detail.value.blank?
+ content += "将优先级由#{old_value}更改为#{new_value}"
+ end
+ return content
+ when 'status_id'
+ old_value = IssueStatus.find_by_id(detail.old_value)&.name
+ new_value = IssueStatus.find_by_id(detail.value)&.name
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "将状态设置为#{new_value}"
+ else
+ new_value = "未设置" if detail.value.blank?
+ content += "将状态由#{old_value}更改为#{new_value}"
+ end
+ case self.issue.pm_issue_type.to_i
+ when 1
+ content.gsub!('新增', '待评审')
+ content.gsub!('正在解决', '进行中')
+ content.gsub!('已解决', '已完成')
+ content.gsub!('关闭', '已关闭')
+ content.gsub!('拒绝', '已拒绝')
+ when 2
+ content.gsub!('新增', '待处理')
+ content.gsub!('正在解决', '进行中')
+ content.gsub!('已解决', '已完成')
+ content.gsub!('关闭', '已关闭')
+ content.gsub!('拒绝', '已拒绝')
+ when 3
+ content.gsub!('新增', '待修复')
+ content.gsub!('正在解决', '修复中')
+ content.gsub!('已解决', '已修复')
+ content.gsub!('关闭', '已关闭')
+ content.gsub!('拒绝', '已拒绝')
+ end
+ return content
+ when 'pm_issue_type'
+ old_value = detail.old_value
+ new_value = detail.value
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "将工作项类型设置为#{new_value}"
+ else
+ new_value = "未设置" if detail.value.blank?
+ content += "将工作项类型由#{old_value}更改为#{new_value}"
+ end
+ content.gsub!('1', '需求')
+ content.gsub!('2', '任务')
+ content.gsub!('3', '缺陷')
+ return content
+ when 'pm_sprint_id'
+ old_value = detail.old_value
+ new_value = detail.value
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加了关联迭代"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将关联迭代更改为未设置"
+ else
+ content += "变更了关联迭代"
+ end
+ end
+ return content
+ when 'project_id'
+ old_value = Project.find_by_id(detail.old_value)&.name
+ new_value = Project.find_by_id(detail.value)&.name
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加关联代码库#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将关联代码库更改为未设置"
+ else
+ content += "将关联代码库由#{old_value}改为#{new_value}"
+ end
+ end
+ return content
+ when 'branch_name'
+ old_value = detail.old_value
+ new_value = detail.value
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加关联分支#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将关联分支更改为未设置"
+ else
+ content += "将关联分支由#{old_value}改为#{new_value}"
+ end
+ end
+ return content
+
+ when 'start_date'
+ old_value = detail.old_value
+ new_value = detail.value
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加开始时间#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将开始时间更改为未设置"
+ else
+ content += "将开始时间由#{old_value}改为#{new_value}"
+ end
+ end
+ return content
+
+ when 'due_date'
+ old_value = detail.old_value
+ new_value = detail.value
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加结束时间#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将结束时间更改为未设置"
+ else
+ content += "将结束时间由#{old_value}改为#{new_value}"
+ end
+ end
+ return content
+ when 'time_scale'
+ old_value = detail.old_value
+ new_value = detail.value
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加预估工时#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将预估工时更改为未设置"
+ else
+ content += "将预估工时由#{old_value}改为#{new_value}"
+ end
+ end
+ return content
+ end
+ when 'attachment'
+ old_value = detail.old_value.to_s
+ new_value = detail.value.to_s
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "上传了附件"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "删除了附件"
+ else
+ content += "上传了附件"
+ end
+ end
+ return content
+ when 'assigner'
+ old_value = User.where(id: detail.old_value.split(",")).map{|u| "#{u.real_name}"}.join("、")
+ new_value = User.where(id: detail.value.split(",")).map{|u| "#{u.real_name}"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加负责人#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将负责人更改为未设置"
+ else
+ content += "将负责人由#{old_value}更改为#{new_value}"
+ end
+ end
+ return content
+ when 'issue_tag'
+ old_value = IssueTag.where(id: detail.old_value.split(",")).map{|t| "#{t.name}"}.join("、")
+ new_value = IssueTag.where(id: detail.value.split(",")).map{|t| "#{t.name}"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "添加标记#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "将标记更改为未设置"
+ else
+ content += "将标记由#{old_value}更改为#{new_value}"
+ end
+ end
+ return content
+ when 'link_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "[#{i.pm_issue_type}]-<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "[#{i.pm_issue_type}]-<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "新建了关联的工作项#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "删除了关联的工作项#{old_value}"
+ else
+ content += "新建了关联的工作项#{new_value}"
+ end
+ end
+ content.gsub!('1', "需求")
+ content.gsub!('2', "任务")
+ content.gsub!('3', "缺陷")
+ return content
+ when 'tag_link_issue'
+ old_value = Issue.where(id: detail.old_value.to_s.split(",")).map{|i| "[#{i.pm_issue_type}]-<#{i.subject}>"}.join("、")
+ new_value = Issue.where(id: detail.value.to_s.split(",")).map{|i| "[#{i.pm_issue_type}]-<#{i.subject}>"}.join("、")
+ if detail.old_value.nil? || detail.old_value.blank?
+ content += "关联了工作项#{new_value}"
+ else
+ if detail.value.nil? || detail.value.blank?
+ content += "取消了关联的工作项#{old_value}"
+ else
+ content += "关联了工作项#{new_value}"
+ end
+ end
+ content.gsub!('1', "需求")
+ content.gsub!('2', "任务")
+ content.gsub!('3', "缺陷")
+ return content
+ when 'issue'
+ issue = self.issue
+ case issue.pm_issue_type
+ when 1
+ content += "创建了需求"
+ when 2
+ content += "创建了任务"
+ when 3
+ content += "创建了缺陷"
+ end
+ end
+
+ return content
+ end
+
+ def operate_content
+ content = "#{operate_by_content}"
detail = self.journal_details.take
case detail.property
when 'issue'
- return "创建了疑修"
+ content += "创建了疑修"
when 'attachment'
- old_value = Attachment.where("id in (?) or uuid in (?)", detail.old_value.to_s.split(","), detail.old_value.to_s.split(",")).pluck(:filename).join("、")
- new_value = Attachment.where("id in (?) or uuid in (?)", detail.value.to_s.split(","), detail.value.to_s.split(",")).pluck(:filename).join("、")
+ old_value = Attachment.where("BINARY id in (?) or uuid in (?)", detail.old_value.to_s.split(","), detail.old_value.to_s.split(",")).pluck(:filename).join("、")
+ new_value = Attachment.where("BINARY id in (?) or uuid in (?)", detail.value.to_s.split(","), detail.value.to_s.split(",")).pluck(:filename).join("、")
if old_value.nil? || old_value.blank?
content += "添加了#{new_value}附件"
else
@@ -116,12 +563,12 @@ class Journal < ApplicationRecord
content += "将负责人由#{old_value}更改为#{new_value}"
end
when 'attr'
- content = "将"
+ content += "将"
case detail.prop_key
when 'subject'
- return "修改了标题"
+ content += "修改了标题"
when 'description'
- return "修改了描述"
+ content += "修改了描述"
when 'status_id'
old_value = IssueStatus.find_by_id(detail.old_value)&.name
new_value = IssueStatus.find_by_id(detail.value)&.name
diff --git a/app/models/member.rb b/app/models/member.rb
index aaaf34efc..521f939c5 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -11,7 +11,6 @@
# course_group_id :integer default("0")
# is_collect :integer default("1")
# graduation_group_id :integer default("0")
-# is_apply_signature :boolean default("0")
# team_user_id :integer
#
# Indexes
diff --git a/app/models/open_users/ci4s.rb b/app/models/open_users/ci4s.rb
new file mode 100644
index 000000000..8a9e1f54c
--- /dev/null
+++ b/app/models/open_users/ci4s.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+#
+# Table name: open_users
+#
+# id :integer not null, primary key
+# user_id :integer
+# type :string(255)
+# uid :string(255)
+# created_at :datetime not null
+# updated_at :datetime not null
+# extra :text(65535)
+#
+# Indexes
+#
+# index_open_users_on_type_and_uid (type,uid) UNIQUE
+# index_open_users_on_user_id (user_id)
+#
+
+class OpenUsers::Ci4s < OpenUser
+ def nickname
+ extra&.[]('username')
+ end
+
+ def en_type
+ 'ci4s'
+ end
+end
diff --git a/app/models/organization_user.rb b/app/models/organization_user.rb
index 900710a9a..4ff6946b7 100644
--- a/app/models/organization_user.rb
+++ b/app/models/organization_user.rb
@@ -5,7 +5,6 @@
# id :integer not null, primary key
# user_id :integer
# organization_id :integer
-# is_creator :boolean default("0")
# created_at :datetime not null
# updated_at :datetime not null
#
diff --git a/app/models/pm_link.rb b/app/models/pm_link.rb
new file mode 100644
index 000000000..d9d9a7893
--- /dev/null
+++ b/app/models/pm_link.rb
@@ -0,0 +1,28 @@
+# == Schema Information
+#
+# Table name: pm_links
+#
+# id :integer not null, primary key
+# be_linkable_type :string(255) not null
+# be_linkable_id :integer not null
+# linkable_type :string(255) not null
+# linkable_id :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+# Indexes
+#
+# index_pm_links_on_linkable_id (linkable_id)
+# index_pm_links_on_linkable_type (linkable_type)
+#
+
+class PmLink < ApplicationRecord
+ belongs_to :linkable, polymorphic: true
+ belongs_to :be_linkable, polymorphic: true
+ # belongs_to :linkable_issue, -> {where(pm_links: {linkable_type: 'Issue'})}, foreign_key: 'linkable_id', class_name: 'Issue'
+ # belongs_to :be_linkable_issue, -> {where(pm_links: {be_linkable_type: 'Issue'})}, foreign_key: 'be_linkable_id', class_name: 'Issue'
+
+ def be_linkable
+ be_linkable_type.constantize.find be_linkable_id
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index ba0cab97e..b0133eec7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -59,6 +59,7 @@
# is_pinned :boolean default("0")
# recommend_index :integer default("0")
# pr_view_admin :boolean default("0")
+# has_actions :boolean default("0")
#
# Indexes
#
@@ -130,9 +131,9 @@ class Project < ApplicationRecord
has_many :pinned_projects, dependent: :destroy
has_many :has_pinned_users, through: :pinned_projects, source: :owner
has_many :webhooks, class_name: "Gitea::Webhook", primary_key: :gpid, foreign_key: :repo_id
- has_many :user_trace_tasks, dependent: :destroy
+ has_many :user_trace_tasks, dependent: :destroy
has_many :project_invite_links, dependent: :destroy
- has_many :project_topic_ralates, dependent: :destroy
+ has_many :project_topic_ralates, dependent: :destroy
has_many :project_topics, through: :project_topic_ralates
has_many :commit_logs, dependent: :destroy
has_many :daily_project_statistics, dependent: :destroy
@@ -149,6 +150,12 @@ class Project < ApplicationRecord
scope :recommend, -> { visible.project_statics_select.where(recommend: true) }
scope :pinned, -> {where(is_pinned: true)}
+ scope :like, lambda { |keywords|
+ # 表情处理
+ keywords = keywords.to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
+ sql = "name LIKE :search OR identifier LIKE :search"
+ where(sql, :search => "%#{keywords.strip}%") unless keywords.blank?
+ }
delegate :content, to: :project_detail, allow_nil: true
delegate :name, to: :license, prefix: true, allow_nil: true
@@ -245,11 +252,11 @@ class Project < ApplicationRecord
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
+ # unless self.is_public
+ # self.recommend = false
+ # self.recommend_index = 0
+ # self.is_pinned = false
+ # end
end
def self.search_project(search)
@@ -393,11 +400,13 @@ class Project < ApplicationRecord
user = Owner.find_by_login namespace_path
user = User.new(login: namespace_path) if user.nil?
+ project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
+ user = User.new(login: namespace_path) if user.nil?
if identifier.end_with?('.json')
project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
identifier = identifier.sub(/.*\K.json/, '')
project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
- else
+ else
project = user&.projects&.find_by(identifier: identifier) || Project.find_by(identifier: "#{namespace_path}/#{identifier}")
end
return nil if project.blank?
@@ -444,36 +453,46 @@ class Project < ApplicationRecord
end
def get_last_project_issues_index
- last_issue = self.issues.issue_issue.last
+ last_issue = self.issues.issue_issue.last
deleted_issue_count = ($redis_cache.hget("issue_cache_delete_count", self.id) || 0).to_i
last_issue&.project_issues_index.present? ? last_issue.project_issues_index + deleted_issue_count : 0
end
def incre_project_issue_cache_delete_count(count=1)
- $redis_cache.hincrby("issue_cache_delete_count", self.id, count)
+ $redis_cache.hincrby("issue_cache_delete_count", self.id, count)
end
def del_project_issue_cache_delete_count
- $redis_cache.hdel("issue_cache_delete_count", self.id)
+ $redis_cache.hdel("issue_cache_delete_count", self.id)
end
def open_portrait
EduSetting.get("open_portrait_projects").present? ? EduSetting.get("open_portrait_projects").split(",").include?(self.id.to_s) : false
end
+ def actionable
+ return false if EduSetting.get("project_user_actionable").nil?
+ return EduSetting.get("project_user_actionable").split(",").include?(self.owner&.login)
+ end
+
def has_pull_request(branch_name)
return true if self.pull_requests.opening.where(head: branch_name).present? || self.pull_requests.opening.where(base: branch_name).present?
if self.forked_from_project_id.present?
return true if self.fork_project.pull_requests.opening.where(head: branch_name).present? || self.fork_project.pull_requests.opening.where(base: branch_name).present?
end
-
+
return false
end
- def self.mindspore_contributors
+ def is_need_apply
+ return false if EduSetting.get("project_need_apply").nil?
+ return EduSetting.get("project_need_apply").split(",").include?(self.id.to_s)
+ end
+
+ def self.mindspore_contributors
cache_result = $redis_cache.get("ProjectMindsporeContributors")
- if cache_result.nil?
+ if cache_result.nil?
contributors = []
file = File.open('public/mindspore_authors', 'r')
file.each_line do |l|
@@ -481,12 +500,12 @@ class Project < ApplicationRecord
email = itemArray[0]
username = itemArray[1]
commits = itemArray[2].to_i
- user = User.find_by(login: username, mail: email)
- user = User.find_by(login: username) if user.nil?
+ user = User.find_by(login: username, mail: email)
+ user = User.find_by(login: username) if user.nil?
user = User.find_by(mail: email) if user.nil?
# next if user.nil?
search_contributor = contributors.select{|con| con["id"]==user.id}[0]
- if search_contributor.present?
+ if search_contributor.present?
search_contributor["contributions"] += commits
else
contributors << {contributions: commits, name: username, login: username, email: email, id: user&.id}.stringify_keys
@@ -497,10 +516,15 @@ class Project < ApplicationRecord
$redis_cache.set("ProjectMindsporeContributors", contributors.to_json)
return contributors
- else
+ else
return JSON.parse(cache_result)
end
end
+
+ def full_url
+ Rails.application.config_for(:configuration)['platform_url'].to_s + '/' + self.owner&.try(:login).to_s + '/' + self.identifier.to_s
+ end
+
def to_builder
Jbuilder.new do |project|
project.id self.id
@@ -509,9 +533,9 @@ class Project < ApplicationRecord
project.description Nokogiri::HTML(self.description).text
project.visits self.visits
project.praises_count self.praises_count.to_i
- project.watchers_count self.watchers_count.to_i
- project.issues_count self.issues_count.to_i
- project.pull_requests_count self.pull_requests_count.to_i
+ project.watchers_count self.watchers_count.to_i
+ project.issues_count self.issues_count.to_i
+ project.pull_requests_count self.pull_requests_count.to_i
project.forked_count self.forked_count.to_i
project.is_public self.is_public
project.mirror_url self.repository&.mirror_url
diff --git a/app/models/project_category.rb b/app/models/project_category.rb
index bc6f8427d..97a304259 100644
--- a/app/models/project_category.rb
+++ b/app/models/project_category.rb
@@ -15,7 +15,6 @@
# Indexes
#
# index_project_categories_on_ancestry (ancestry)
-# index_project_categories_on_id (id)
#
class ProjectCategory < ApplicationRecord
diff --git a/app/models/project_language.rb b/app/models/project_language.rb
index 22a4a81ff..0770a1efa 100644
--- a/app/models/project_language.rb
+++ b/app/models/project_language.rb
@@ -9,10 +9,6 @@
# created_at :datetime not null
# updated_at :datetime not null
#
-# Indexes
-#
-# index_project_languages_on_id (id)
-#
class ProjectLanguage < ApplicationRecord
include Projectable
diff --git a/app/models/project_topic_ralate.rb b/app/models/project_topic_ralate.rb
index d8638699f..5c4c760a1 100644
--- a/app/models/project_topic_ralate.rb
+++ b/app/models/project_topic_ralate.rb
@@ -19,4 +19,8 @@ class ProjectTopicRalate < ApplicationRecord
belongs_to :project_topic, counter_cache: :projects_count
belongs_to :project
+
+ validates :project_id, uniqueness: { scope: :project_topic_id }
+
+
end
diff --git a/app/models/pull_request.rb b/app/models/pull_request.rb
index 26b4ce2c6..8d276625e 100644
--- a/app/models/pull_request.rb
+++ b/app/models/pull_request.rb
@@ -34,7 +34,7 @@ class PullRequest < ApplicationRecord
belongs_to :issue
belongs_to :user
- belongs_to :project, counter_cache: true, touch: true
+ belongs_to :project, counter_cache: true, touch: true, optional: true
belongs_to :fork_project, class_name: 'Project', foreign_key: :fork_project_id, optional: true
has_many :pull_request_assigns, foreign_key: :pull_request_id
has_many :pull_request_tags, foreign_key: :pull_request_id
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7d3f207ea..f2815dde7 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -27,7 +27,6 @@
#
# Indexes
#
-# index_name (project_id)
# index_repositories_on_identifier (identifier)
# index_repositories_on_project_id (project_id)
# index_repositories_on_user_id (user_id)
diff --git a/app/models/sync_repositories/gitee.rb b/app/models/sync_repositories/gitee.rb
index 0a51b21c8..29309c99b 100644
--- a/app/models/sync_repositories/gitee.rb
+++ b/app/models/sync_repositories/gitee.rb
@@ -11,6 +11,8 @@
# sync_direction :integer
# created_at :datetime not null
# updated_at :datetime not null
+# external_token :string(255)
+# webhook_gid :integer
#
# Indexes
#
diff --git a/app/models/sync_repositories/github.rb b/app/models/sync_repositories/github.rb
index 1ef413a54..10845dea7 100644
--- a/app/models/sync_repositories/github.rb
+++ b/app/models/sync_repositories/github.rb
@@ -11,6 +11,8 @@
# sync_direction :integer
# created_at :datetime not null
# updated_at :datetime not null
+# external_token :string(255)
+# webhook_gid :integer
#
# Indexes
#
diff --git a/app/models/token.rb b/app/models/token.rb
index fac516eb8..9b9ea5895 100644
--- a/app/models/token.rb
+++ b/app/models/token.rb
@@ -15,6 +15,7 @@
#
+
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -44,7 +45,7 @@ class Token < ActiveRecord::Base
def self.get_or_create_permanent_login_token(user, type)
token = Token.get_token_from_user(user, type)
- Rails.logger.info "###### Token.get_token_from_user result: #{token&.value}"
+ Rails.logger.info "###### Token.get_token_from_user time:#{Time.new.to_i}, result: #{token&.value}"
unless token
token = Token.create(:user => user, :action => type)
Rails.logger.info "###### Token.get_token_from_user is nul and agine create token: #{token&.value}"
@@ -117,8 +118,8 @@ class Token < ActiveRecord::Base
# Removes obsolete tokens (same user and action)
def delete_previous_tokens
- if user
- Token.where(['user_id = ? AND action = ?', user.id, action]).delete_all
- end
+ # if user
+ # Token.where(['user_id = ? AND action = ?', user.id, action]).delete_all
+ # end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 988f6d8c2..a65775225 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -137,7 +137,7 @@ class User < Owner
has_many :tidings, :dependent => :destroy
# has_many :journals_for_messages, :as => :jour, :dependent => :destroy
- has_many :attachments,foreign_key: :author_id, :dependent => :destroy
+ has_many :attachments,foreign_key: :author_id
has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', dependent: :destroy
@@ -160,7 +160,7 @@ class User < Owner
# 教学案例
# has_many :libraries, dependent: :destroy
- has_many :project_trends, dependent: :destroy
+ has_many :project_trends
has_many :oauths , dependent: :destroy
has_many :organization_users, dependent: :destroy
@@ -168,8 +168,8 @@ class User < Owner
has_many :pinned_projects, dependent: :destroy
has_many :is_pinned_projects, through: :pinned_projects, source: :project
accepts_nested_attributes_for :is_pinned_projects
- has_many :issues, dependent: :destroy, foreign_key: :author_id
- has_many :pull_requests, dependent: :destroy
+ has_many :issues, foreign_key: :author_id
+ has_many :pull_requests
has_many :public_keys, class_name: "Gitea::PublicKey",primary_key: :gitea_uid, foreign_key: :owner_id, dependent: :destroy
has_one :user_template_message_setting, dependent: :destroy
@@ -692,7 +692,11 @@ class User < Owner
# Returns the user who matches the given autologin +key+ or nil
def self.try_to_autologin(key)
user = Token.find_active_user('autologin', key)
- user.update(last_login_on: Time.now) if user
+ if user
+ Rails.cache.fetch("user::update::last_login_on::#{user.id}",:expires_in => 5.minutes) do
+ user.update(last_login_on: Time.now)
+ end
+ end
user
end
diff --git a/app/models/user_action.rb b/app/models/user_action.rb
index b4173fe77..289fa188e 100644
--- a/app/models/user_action.rb
+++ b/app/models/user_action.rb
@@ -10,6 +10,10 @@
# updated_at :datetime not null
# ip :string(255)
# data_bank :text(65535)
+# login :string(255)
+# phone :string(255)
+# email :string(255)
+# memo :text(65535)
#
# Indexes
#
@@ -19,5 +23,76 @@
# index_user_actions_on_user_id (user_id)
#
-class UserAction < ApplicationRecord
-end
+class UserAction < ApplicationRecord
+
+ after_save :add_user_info
+
+ serialize :data_bank, JSON
+
+ def action_name
+ case action_type
+ when "DestroyUser" then "用户注销"
+ when "DestroyProject" then "删除项目"
+ when "Login" then "登录"
+ when "Logout" then "退出登录"
+ else self.action_type
+ end
+ end
+
+ def opt_user_name
+ user = User.find_by(id: self.user_id)
+ if user.present?
+ user&.login
+ else
+ del_user = UserAction.find_by(action_type: "DestroyUser", action_id: self.user_id)
+ del_user.present? ? del_user.user.login : "不存在用户:#{user_id}"
+ end
+ end
+
+ def action_info
+ case action_type
+ when "DestroyUser" then "账号:#{user&.login}
邮箱:#{user&.mail}
手机号:#{user&.phone}
昵称:#{user&.nickname}
"
+ when "DestroyProject" then "项目名称:#{project&.name}
项目标识:#{project&.identifier}
"
+ else "--"
+ end
+ end
+
+ def user
+ action_user = if action_type == "DestroyUser" && data_bank.present?
+ build_mode("User")
+ else
+ User.find_by(id: self.user_id)
+ end
+ action_user
+ end
+
+ def project
+ action_project = if action_type == "DestroyProject" && data_bank.present?
+ build_mode("Project")
+ else
+ Project.find_by(id: self.action_id)
+ end
+ action_project
+ end
+ def build_mode(model_name)
+ model = model_name.constantize.new
+ model_name.constantize.column_names.each do |col|
+ data = self.data_bank.class == String ? JSON.parse(self.data_bank) : self.data_bank
+ model[col] = data[col]
+ end
+ model
+ rescue =>err
+ return nil
+ end
+
+ private
+ def add_user_info
+ if self.login.blank?
+ if user.present?
+ self.login = user.login
+ self.email = user.mail
+ self.phone = user.phone
+ end
+ end
+ end
+end
diff --git a/app/models/user_extension.rb b/app/models/user_extension.rb
index aeb9a9d83..ef4af5fd3 100644
--- a/app/models/user_extension.rb
+++ b/app/models/user_extension.rb
@@ -22,9 +22,9 @@
# school_id :integer
# description :string(255)
# department_id :integer
-# province :text(65535)
-# custom_department :string(255)
+# province :string(255)
# city :string(255)
+# custom_department :string(255)
# show_email :boolean default("0")
# show_location :boolean default("0")
# show_department :boolean default("0")
diff --git a/app/queries/application_query.rb b/app/queries/application_query.rb
index e2d7c446f..4d43bfeb6 100644
--- a/app/queries/application_query.rb
+++ b/app/queries/application_query.rb
@@ -24,6 +24,26 @@ class ApplicationQuery
end
end
+ # find one repo that a user has tokens
+ def find_one_balance_with_token(user_id, project_id)
+ # return 3 statuses: UnknownErr/ResUserNotExisted/Success
+ params = {
+ "request-type": "query user balance of single repo",
+ "username": user_id.to_s,
+ "token_name": project_id.to_s
+ }.to_json
+ Rails.logger.info "query user balance of single repo params======= #{params}"
+ resp_body = Blockchain::InvokeBlockchainApi.call(params)
+ Rails.logger.info "resp_body======= #{resp_body}"
+ if resp_body['status'] == 0
+ return resp_body
+ elsif resp_body['status'] == 100
+ return 0 # 找不到用户返回0
+ else
+ raise "区块链接口请求失败."
+ end
+ end
+
# find one repo that a user has tokens
def find_one_balance(user_id, project_id)
diff --git a/app/queries/blockchain/balance_query.rb b/app/queries/blockchain/balance_query.rb
index f2c39c003..d50b38ee6 100644
--- a/app/queries/blockchain/balance_query.rb
+++ b/app/queries/blockchain/balance_query.rb
@@ -9,24 +9,45 @@ class Blockchain::BalanceQuery < ApplicationQuery
def call
if is_current_admin_user
- token_list, total_count = find_repo_with_token(params["user_id"], (params["page"] || 1), (params["limit"] || 10))
result_list = []
- token_list.each do |t|
- project = Project.find_by(id: t['token_name'].to_i)
- if project.nil?
- result_list << {project: nil, balance: t['balance']}
- next
+ if params[:project_id].present? or params[:keyword].present?
+ token_list, total_count = find_repo_with_token(params["user_id"], 1, 10000)
+ p_ids = []
+ token_list.each do |t|
+ p_ids.push(t['token_name'].to_i)
end
- owner = User.find_by(id: project.user_id)
- if owner.nil? || project.nil?
- else
- balance = t['balance']
- result_list << {project: project, balance: balance}
+ project_ids = params[:project_id].present? ? [params[:project_id]] : Project.where(id: p_ids).like(params[:keyword]).ids
+ project_ids.each do |project_id|
+ project_balance = find_one_balance_with_token(params["user_id"], project_id)
+ next if project_balance == 0
+ project = Project.find_by(id: project_balance['token_name'].to_i)
+ if project.present?
+ owner = User.find_by(id: project.user_id)
+ if owner.present?
+ result_list << {project: project, balance: project_balance['balance']}
+ end
+ end
end
+ {"status": 0, "projects": result_list, "total_count": result_list.size}
+ else
+ token_list, total_count = find_repo_with_token(params["user_id"], (params["page"] || 1), (params["limit"] || 10))
+ token_list.each do |t|
+ project = Project.find_by(id: t['token_name'].to_i)
+ if project.nil?
+ result_list << {project: nil, balance: t['balance']}
+ next
+ end
+ owner = User.find_by(id: project.user_id)
+ if owner.nil? || project.nil?
+ else
+ balance = t['balance']
+ result_list << {project: project, balance: balance}
+ end
+ end
+ {"status": 0, "projects": result_list, "total_count": total_count}
end
- results = {"status": 0, "projects": result_list, "total_count": total_count}
else
- results = {"status": 1} # query failed
+ {"status": 1} # query failed
end
end
end
diff --git a/app/queries/projects/list_my_query.rb b/app/queries/projects/list_my_query.rb
index b81d5542b..32a9ee787 100644
--- a/app/queries/projects/list_my_query.rb
+++ b/app/queries/projects/list_my_query.rb
@@ -63,6 +63,15 @@ class Projects::ListMyQuery < ApplicationQuery
projects = projects.sync_mirror
end
+ if params[:topic_name].present?
+ projects = projects.with_project_topic_name(params[:topic_name].to_s.split(","))
+ end
+
+ if params[:topic_id].present?
+ projects = projects.with_project_topic(params[:topic_id])
+ end
+
+
# 表情处理
keywords = params[:search].to_s.each_char.select { |c| c.bytes.first < 240 }.join('')
q = projects.ransack(name_or_identifier_cont: keywords)
@@ -79,7 +88,9 @@ class Projects::ListMyQuery < ApplicationQuery
else
if @home_top_ids.present?
scope = scope.distinct.order("FIELD(projects.id, #{@home_top_ids.join(",")}) desc, projects.#{sort} #{sort_direction}")
- else
+ elsif params[:topic_name].present?
+ scope = scope.distinct.order("project_topics.id asc, projects.#{sort} #{sort_direction}")
+ else
scope = scope.distinct.order("projects.#{sort} #{sort_direction}")
end
end
diff --git a/app/services/accounts/reset_password_service.rb b/app/services/accounts/reset_password_service.rb
index 5202fe77a..58d0fda8b 100644
--- a/app/services/accounts/reset_password_service.rb
+++ b/app/services/accounts/reset_password_service.rb
@@ -1,10 +1,11 @@
module Accounts
class ResetPasswordService < ApplicationService
+ include AesCryptHelper
# login、code、password、password_confirmation
def initialize(user, params)
@user = user
- @password = params[:password]
- @password_confirmation = params[:password_confirmation]
+ @password = decrypt(params[:password]) rescue ""
+ @password_confirmation = decrypt(params[:password_confirmation]) rescue ""
end
def call
diff --git a/app/services/api/pm/issues/batch_delete_service.rb b/app/services/api/pm/issues/batch_delete_service.rb
new file mode 100644
index 000000000..9afdfdb81
--- /dev/null
+++ b/app/services/api/pm/issues/batch_delete_service.rb
@@ -0,0 +1,38 @@
+class Api::Pm::Issues::BatchDeleteService < ApplicationService
+ include ActiveModel::Model
+
+ attr_reader :project, :issues, :current_user
+
+ validates :project, :issues, :current_user, presence: true
+
+ def initialize(project, issues, current_user = nil)
+ @project = project
+ @issues = issues.includes(:assigners)
+ @current_user = current_user
+ end
+
+ def call
+ raise Error, errors.full_messages.join(", ") unless valid?
+ try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
+
+ delete_issues
+
+ project.incre_project_issue_cache_delete_count(@issues.size)
+
+ if Site.has_notice_menu? && !project.id.zero?
+ @issues.each do |issue|
+ SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id)
+ end
+ end
+
+ unlock("Api::V1::Issues::DeleteService:#{project.id}")
+
+ return true
+ end
+
+ private
+
+ def delete_issues
+ raise Error, "批量删除疑修失败!" unless @issues.destroy_all
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/pm/issues/batch_update_service.rb b/app/services/api/pm/issues/batch_update_service.rb
new file mode 100644
index 000000000..b7eabf2cb
--- /dev/null
+++ b/app/services/api/pm/issues/batch_update_service.rb
@@ -0,0 +1,33 @@
+class Api::Pm::Issues::BatchUpdateService < ApplicationService
+ include ActiveModel::Model
+ include Api::V1::Issues::Concerns::Checkable
+ include Api::V1::Issues::Concerns::Loadable
+
+ attr_reader :project, :issues, :params, :current_user
+ attr_reader :status_id, :priority_id, :milestone_id, :project_id
+ attr_reader :issue_tag_ids, :assigner_ids
+
+ validates :project, :issues, :current_user, presence: true
+
+ def initialize(project, issues, params, current_user = nil)
+ @project = project
+ @issues = issues
+ @params = params
+ @current_user = current_user
+ end
+
+ def call
+ raise Error, errors.full_messages.join(", ") unless valid?
+ ActiveRecord::Base.transaction do
+ @issues.each do |issue|
+ if issue.issue_classify == "issue"
+ Api::Pm::Issues::UpdateService.call(project, issue, params, current_user)
+ end
+ end
+
+ return true
+ end
+ end
+
+
+end
\ No newline at end of file
diff --git a/app/services/api/pm/issues/create_service.rb b/app/services/api/pm/issues/create_service.rb
new file mode 100644
index 000000000..5412fe17d
--- /dev/null
+++ b/app/services/api/pm/issues/create_service.rb
@@ -0,0 +1,181 @@
+class Api::Pm::Issues::CreateService < ApplicationService
+ include ActiveModel::Model
+ include Api::V1::Issues::Concerns::Checkable
+ include Api::V1::Issues::Concerns::Loadable
+
+ attr_reader :project, :current_user
+ attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num, :root_subject
+ attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login
+ attr_accessor :created_issue
+
+ validates :subject, presence: true
+ validates :status_id, :priority_id, presence: true
+ validates :project, :current_user, presence: true
+ validates :blockchain_token_num, numericality: {greater_than: 0}, allow_blank: true
+
+ def initialize(project, params, current_user = nil)
+ @project = project
+ @current_user = current_user
+ @status_id = params[:status_id]
+ @priority_id = params[:priority_id]
+ @milestone_id = params[:milestone_id]
+ @branch_name = params[:branch_name]
+ @start_date = params[:start_date]
+ @due_date = params[:due_date]
+ @subject = params[:subject]
+ @description = params[:description]
+ @blockchain_token_num = params[:blockchain_token_num]
+ @issue_tag_ids = params[:issue_tag_ids]
+ @assigner_ids = params[:assigner_ids]
+ @attachment_ids = params[:attachment_ids]
+ @receivers_login = params[:receivers_login]
+ @pm_project_id = params[:pm_project_id]
+ @pm_sprint_id = params[:pm_sprint_id]
+ @pm_issue_type = params[:pm_issue_type]
+ @root_id = params[:root_id]
+ @time_scale = params[:time_scale]
+ @linkable_id = params[:link_able_id]
+ @root_subject = params[:root_subject]
+ end
+
+ def call
+ raise Error, errors.full_messages.join(', ') unless valid?
+ ActiveRecord::Base.transaction do
+ check_issue_status(status_id)
+ check_issue_priority(priority_id)
+ check_milestone(milestone_id) if milestone_id.present?
+ check_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
+ check_assigners(assigner_ids) unless assigner_ids.blank?
+ check_attachments(attachment_ids) unless attachment_ids.blank?
+ check_atme_receivers(receivers_login) unless receivers_login.blank?
+ check_blockchain_token_num(current_user.id, project.id, blockchain_token_num) if blockchain_token_num.present?
+ load_assigners(assigner_ids) unless assigner_ids.blank?
+ load_attachments(attachment_ids) unless attachment_ids.blank?
+ load_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
+ load_atme_receivers(receivers_login) unless receivers_login.blank?
+ try_lock("Api::Pm::Issues::CreateService:#{project.id}") # 开始写数据,加锁
+ @created_issue = Issue.new(issue_attributes)
+ @created_issue.pm_issue_type = @pm_issue_type
+ if @root_subject.present? && @pm_issue_type.to_i == 4
+ @root_issue = Issue.find_by(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id)
+ unless @root_issue.present?
+ @root_issue = Issue.create(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id, status_id: 1, priority_id: 1, tracker_id: Tracker.first.id, project_id: @project.id, author_id: current_user.id)
+ end
+ @created_issue.root_id = @root_issue.id
+ else
+ @created_issue.root_id = @root_id
+ end
+ build_author_participants
+ build_assigner_participants unless assigner_ids.blank?
+ build_atme_participants if @atme_receivers.present?
+ build_issue_journal_details
+ build_issue_project_trends
+ @created_issue.assigners = @assigners unless assigner_ids.blank?
+ @created_issue.attachments = @attachments unless attachment_ids.blank?
+ @created_issue.issue_tags = @issue_tags unless issue_tag_ids.blank?
+ @created_issue.pm_project_id = @pm_project_id
+ @created_issue.pm_sprint_id = @pm_sprint_id
+ @created_issue.time_scale = @time_scale
+ @created_issue.issue_tags_value = @issue_tags.order('id asc').pluck(:id).join(',') unless issue_tag_ids.blank?
+ @created_issue.changer_id = @current_user.id
+ @created_issue.save!
+ if @created_issue.parent_issue.present?
+ parent_issue = @created_issue.parent_issue
+ if @created_issue.root_id.present? && parent_issue.present?
+ journal = parent_issue.journals.create!({user_id: current_user.id})
+ journal.journal_details.create!({property: @created_issue.pm_issue_type_string, prop_key: 'leaf_issue', value: @created_issue.id.to_s})
+ end
+ end
+ if @linkable_id.present?
+ PmLink.create(be_linkable_type: 'Issue', be_linkable_id: @created_issue.id, linkable_type: 'Issue', linkable_id: @linkable_id)
+ another_issue = Issue.find_by_id(@linkable_id)
+ if another_issue.present?
+ journal = another_issue.journals.create!({user_id: @current_user.id})
+ journal.journal_details.create!({property: 'link_issue', prop_key: "1", value: @created_issue.id.to_s})
+ end
+ end
+ if Site.has_blockchain? && @project.use_blockchain
+ if @created_issue.blockchain_token_num.present? && @created_issue.blockchain_token_num > 0
+ Blockchain::CreateIssue.call({user_id: current_user.id, project_id: @created_issue.project_id, token_num: @created_issue.blockchain_token_num})
+ end
+
+ push_activity_2_blockchain('issue_create', @created_issue)
+ end
+
+ project.del_project_issue_cache_delete_count # 把缓存里存储项目删除issue的个数清除掉
+ unless @project.id.zero?
+ # 新增时向grimoirelab推送事件
+ IssueWebhookJob.set(wait: 5.seconds).perform_later(@created_issue.id)
+
+ # @信息发送
+ AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
+
+ # 发消息
+ if Site.has_notice_menu?
+ SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?
+ SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @created_issue&.id)
+ end
+
+ # 触发webhook
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueCreate', @created_issue&.id, current_user.id)
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @created_issue&.id, current_user.id, {issue_tag_ids: [[], issue_tag_ids]}) unless issue_tag_ids.blank?
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @created_issue&.id, current_user.id, {assigner_ids: [[], assigner_ids]}) unless assigner_ids.blank?
+ end
+ unlock("Api::Pm::Issues::CreateService:#{project.id}") # 结束写数据,解锁
+ end
+
+ return @created_issue
+ end
+
+ private
+
+ def issue_attributes
+ issue_attributes = {
+ subject: subject,
+ project_id: project.id,
+ author_id: current_user.id,
+ tracker_id: Tracker.first.id,
+ status_id: status_id,
+ priority_id: priority_id,
+ project_issues_index: (project.get_last_project_issues_index + 1),
+ issue_type: '1',
+ issue_classify: 'issue'
+ }
+
+ issue_attributes.merge!({description: description}) if description.present?
+ issue_attributes.merge!({fixed_version_id: milestone_id}) if milestone_id.present?
+ issue_attributes.merge!({start_date: start_date}) if start_date.present?
+ issue_attributes.merge!({due_date: due_date}) if due_date.present?
+ issue_attributes.merge!({branch_name: branch_name}) if branch_name.present?
+ issue_attributes.merge!({blockchain_token_num: blockchain_token_num}) if blockchain_token_num.present?
+
+ issue_attributes
+ end
+
+ def build_author_participants
+ @created_issue.issue_participants.new({participant_type: 'authored', participant_id: current_user.id})
+ end
+
+ def build_assigner_participants
+ assigner_ids.each do |aid|
+ @created_issue.issue_participants.new({participant_type: 'assigned', participant_id: aid})
+ end
+ end
+
+ def build_atme_participants
+ @atme_receivers.each do |receiver|
+ @created_issue.issue_participants.new({participant_type: 'atme', participant_id: receiver.id})
+ end
+ end
+
+ def build_issue_project_trends
+ return if @project.id == 0
+ @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: 'create'})
+ @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE}) if status_id.to_i == 5
+ end
+
+ def build_issue_journal_details
+ journal = @created_issue.journals.new({user_id: current_user.id})
+ journal.journal_details.new({property: @created_issue.pm_issue_type_string, prop_key: 1, old_value: '', value: ''})
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/pm/issues/delete_service.rb b/app/services/api/pm/issues/delete_service.rb
new file mode 100644
index 000000000..0a04094c3
--- /dev/null
+++ b/app/services/api/pm/issues/delete_service.rb
@@ -0,0 +1,48 @@
+class Api::Pm::Issues::DeleteService < ApplicationService
+ include ActiveModel::Model
+
+ attr_reader :project, :issue, :current_user
+
+ validates :project, :issue, :current_user, presence: true
+
+ def initialize(project, issue, current_user = nil)
+ @project = project
+ @issue = issue
+ @current_user = current_user
+ end
+
+ def call
+ raise Error, errors.full_messages.join(", ") unless valid?
+ try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
+ ActiveRecord::Base.transaction do
+ parent_issue = @issue.parent_issue
+ if @issue.root_id.present? && parent_issue.present?
+ journal = parent_issue.journals.create!({user_id: current_user.id})
+ journal.journal_details.create!({property: @issue.pm_issue_type_string, prop_key: 'leaf_issue', old_value: @issue.id.to_s})
+ end
+
+ delete_issue
+ #删除双向关联
+ PmLink.where(be_linkable_id: @issue.id, be_linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id, linkable_type: 'Issue')).map(&:destroy)
+
+ project.incre_project_issue_cache_delete_count
+
+ if Site.has_blockchain? && @project.use_blockchain && !project.id.zero?
+ unlock_balance_on_blockchain(@issue.author_id.to_s, @project.id.to_s, @issue.blockchain_token_num.to_i) if @issue.blockchain_token_num.present?
+ end
+
+ if Site.has_notice_menu? && !project.id.zero?
+ SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id)
+ end
+ end
+ unlock("Api::V1::Issues::DeleteService:#{project.id}")
+
+ return true
+ end
+
+ private
+
+ def delete_issue
+ raise Error, "删除疑修失败!" unless issue.destroy!
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/pm/issues/update_service.rb b/app/services/api/pm/issues/update_service.rb
new file mode 100644
index 000000000..21119ca9b
--- /dev/null
+++ b/app/services/api/pm/issues/update_service.rb
@@ -0,0 +1,327 @@
+class Api::Pm::Issues::UpdateService < ApplicationService
+ include ActiveModel::Model
+ include Api::V1::Issues::Concerns::Checkable
+ include Api::V1::Issues::Concerns::Loadable
+
+ attr_reader :project, :issue, :current_user, :operate_by
+ attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
+ attr_reader :target_pm_project_id, :pm_sprint_id, :pm_issue_type, :root_id, :time_scale
+ attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login, :before_issue_tag_ids, :before_assigner_ids, :project_id
+ attr_accessor :add_assigner_ids, :previous_issue_changes, :updated_issue, :atme_receivers
+
+ validates :project, :issue, :current_user, presence: true
+ validates :blockchain_token_num, numericality: {greater_than: 0}, allow_blank: true
+
+ def initialize(project, issue, params, current_user = nil, operate_by='Issue')
+ @project = project
+ @issue = issue
+ @current_user = current_user
+ @operate_by = operate_by
+ @status_id = params[:status_id]
+ @priority_id = params[:priority_id]
+ @milestone_id = params[:milestone_id]
+ @branch_name = params[:branch_name]
+ @start_date = params[:start_date]
+ @due_date = params[:due_date]
+ @subject = params[:subject]
+ @description = params[:description]
+ @blockchain_token_num = params[:blockchain_token_num]
+ @issue_tag_ids = params[:issue_tag_ids]
+ @assigner_ids = params[:assigner_ids]
+ @before_issue_tag_ids = issue.issue_tags.pluck(:id)
+ @before_assigner_ids = issue.assigners.pluck(:id)
+ @attachment_ids = params[:attachment_ids]
+ @receivers_login = params[:receivers_login]
+ @target_pm_project_id = params[:target_pm_project_id]
+ @pm_sprint_id = params[:pm_sprint_id]
+ @pm_issue_type = params[:pm_issue_type]
+ @root_id = params[:root_id]
+ @time_scale = params[:time_scale]
+ @project_id = params[:project_id]
+ @add_assigner_ids = []
+ @previous_issue_changes = {}
+ end
+
+ def call
+ raise Error, errors.full_messages.join(", ") unless valid?
+ ActiveRecord::Base.transaction do
+ check_issue_status(status_id) if status_id.present?
+ check_issue_priority(priority_id) if priority_id.present?
+ check_milestone(milestone_id) if milestone_id.present?
+ check_root_issue(issue, root_id) if root_id.present?
+ check_issue_tags(issue_tag_ids) unless issue_tag_ids.nil?
+ check_assigners(assigner_ids) unless assigner_ids.nil?
+ check_attachments(attachment_ids) unless attachment_ids.nil?
+ check_atme_receivers(receivers_login) unless receivers_login.nil?
+ check_blockchain_token_num(issue.author_id, project.id, blockchain_token_num, (@issue.blockchain_token_num || 0)) if blockchain_token_num.present? && current_user.id == @issue.author_id && !PullAttachedIssue.exists?(issue_id: @issue, fixed: true)
+ load_assigners(assigner_ids)
+ load_attachments(attachment_ids)
+ load_issue_tags(issue_tag_ids)
+ load_atme_receivers(receivers_login) unless receivers_login.nil?
+
+ try_lock("Api::Pm::Issues::UpdateService:#{project.id}:#{issue.id}")
+ @updated_issue = @issue
+ issue_load_attributes
+ build_assigner_issue_journal_details unless assigner_ids.nil?# 操作记录
+ build_attachment_issue_journal_details unless attachment_ids.nil?
+ build_issue_tag_issue_journal_details unless issue_tag_ids.nil?
+ build_assigner_participants unless assigner_ids.nil? # 负责人
+ build_edit_participants
+ build_atme_participants if @atme_receivers.present?
+ unless assigner_ids.nil?
+ @previous_issue_changes.merge!(assigned_to_id: [@updated_issue.assigners.pluck(:id), @assigners.pluck(:id)])
+ @updated_issue.assigners = @assigners || User.none
+ end
+ @updated_issue.attachments = @attachments || Attachment.none unless attachment_ids.nil?
+ @updated_issue.issue_tags_relates.destroy_all & @updated_issue.issue_tags = @issue_tags || IssueTag.none unless issue_tag_ids.nil?
+ @updated_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.nil?
+
+ #Pm相关
+ @updated_issue.pm_project_id = @target_pm_project_id unless @target_pm_project_id.nil?
+ @updated_issue.pm_sprint_id = @pm_sprint_id unless @pm_sprint_id.nil?
+ if @updated_issue.children_issues.count == 0 && @updated_issue.parent_id.nil?
+ @updated_issue.pm_issue_type = @pm_issue_type unless @pm_issue_type.nil?
+ end
+ @updated_issue.root_id = @root_id unless @root_id.nil? #不为 nil的时候更新
+ @updated_issue.root_id = nil if @root_id.try(:zero?) #为 0 的时候设置为 nil
+ @updated_issue.time_scale = @time_scale unless @time_scale.nil?
+ @updated_issue.project_id = @project_id unless @project_id.nil?
+ @updated_issue.updated_on = Time.now
+ @updated_issue.changer_id = @current_user.id
+ @updated_issue.save!
+
+ build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
+ build_previous_issue_changes
+ build_issue_project_trends if status_id.present? # 开关时间记录
+ build_cirle_blockchain_token if blockchain_token_num.present?
+ unless @project.id.zero?
+ # @信息发送
+ AtmeService.call(current_user, @atme_receivers, @issue) unless receivers_login.blank?
+ # 消息发送
+ if Site.has_notice_menu?
+ SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_issue_changes) unless previous_issue_changes.blank?
+ SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id, add_assigner_ids) unless add_assigner_ids.blank?
+ end
+ # 触发webhook
+ Rails.logger.info "################### 触发webhook"
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueUpdate', @updated_issue&.id, current_user.id, previous_issue_changes.except(:issue_tags_value, :assigned_to_id))
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @issue&.id, current_user.id, {issue_tag_ids: [before_issue_tag_ids, issue_tag_ids]}) unless issue_tag_ids.nil?
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @issue&.id, current_user.id, {assigner_ids: [before_assigner_ids, assigner_ids]}) unless assigner_ids.nil?
+ end
+
+ unlock("Api::Pm::Issues::UpdateService:#{project.id}:#{issue.id}")
+ return @updated_issue
+ end
+ end
+
+ private
+
+ def issue_load_attributes
+ if current_user.id == @updated_issue.author_id && !PullAttachedIssue.exists?(issue_id: @updated_issue, fixed: true)
+ @updated_issue.blockchain_token_num = blockchain_token_num unless blockchain_token_num.nil?
+ end
+ @updated_issue.status_id = status_id if status_id.present?
+ @updated_issue.priority_id = priority_id if priority_id.present?
+ @updated_issue.fixed_version_id = milestone_id unless milestone_id.nil?
+ @updated_issue.branch_name = branch_name unless branch_name.nil?
+ @updated_issue.start_date = start_date unless start_date.nil?
+ @updated_issue.due_date = due_date unless due_date.nil?
+ @updated_issue.subject = subject if subject.present?
+ @updated_issue.description = description unless description.nil?
+ end
+
+ def build_assigner_participants
+ if assigner_ids.blank?
+ @updated_issue.issue_participants.where(participant_type: "assigned").each(&:destroy!)
+ else
+ @updated_issue.issue_participants.where(participant_type: "assigned").where.not(participant_id: assigner_ids).each(&:destroy!)
+ assigner_ids.each do |aid|
+ next if @updated_issue.issue_participants.exists?(participant_type: "assigned", participant_id: aid)
+ @updated_issue.issue_participants.new({participant_type: "assigned", participant_id: aid})
+ @add_assigner_ids << aid
+ end
+ end
+ end
+
+ def build_edit_participants
+ @updated_issue.issue_participants.new({participant_type: "edited", participant_id: current_user.id}) unless @updated_issue.issue_participants.exists?(participant_type: "edited", participant_id: current_user.id)
+ end
+
+ def build_atme_participants
+ @atme_receivers.each do |receiver|
+ next if @updated_issue.issue_participants.exists?(participant_type: "atme", participant_id: receiver.id)
+ @updated_issue.issue_participants.new({participant_type: "atme", participant_id: receiver.id})
+ end
+ end
+
+ def build_previous_issue_changes
+ @previous_issue_changes.merge!(@updated_issue.previous_changes.slice("status_id", "priority_id", "fixed_version_id", "issue_tags_value", "branch_name", "subject").symbolize_keys)
+ if @updated_issue.previous_changes[:start_date].present?
+ @previous_issue_changes.merge!(start_date: [@updated_issue.previous_changes[:start_date][0].to_s, @updated_issue.previous_changes[:start_date][1].to_s])
+ end
+ if @updated_issue.previous_changes[:due_date].present?
+ @previous_issue_changes.merge!(due_date: [@updated_issue.previous_changes[:due_date][0].to_s, @updated_issue.previous_changes[:due_date][1].to_s])
+ end
+ end
+
+ def build_cirle_blockchain_token
+ if @updated_issue.previous_changes["blockchain_token_num"].present?
+ unlock_balance_on_blockchain(@updated_issue&.author_id.to_s, @updated_issue.project_id.to_s, @updated_issue.previous_changes["blockchain_token_num"][0].to_i) if @updated_issue.previous_changes["blockchain_token_num"][0].present?
+ lock_balance_on_blockchain(@updated_issue&.author_id.to_s, @updated_issue.project_id.to_s, @updated_issue.previous_changes["blockchain_token_num"][1].to_i) if @updated_issue.previous_changes["blockchain_token_num"][1].present?
+ end
+ end
+
+ def build_issue_project_trends
+ if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][1] == 5
+ @updated_issue.project_trends.create!({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE})
+ end
+ if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][0] == 5
+ @updated_issue.project_trends.where(action_type: ProjectTrend::CLOSE).each(&:destroy!)
+ end
+ end
+
+ def build_after_issue_journal_details
+ begin
+ # 更改标题
+ if @updated_issue.previous_changes["subject"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "subject", old_value: @updated_issue.previous_changes["subject"][0], value: @updated_issue.previous_changes["subject"][1]})
+ end
+
+ # 更改描述
+ if @updated_issue.previous_changes["description"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "description", old_value: @updated_issue.previous_changes["description"][0], value: @updated_issue.previous_changes["description"][1]})
+ end
+
+ # 修改状态
+ if @updated_issue.previous_changes["status_id"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "status_id", old_value: @updated_issue.previous_changes["status_id"][0], value: @updated_issue.previous_changes["status_id"][1]})
+ end
+
+ # 修改优先级
+ if @updated_issue.previous_changes["priority_id"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "priority_id", old_value: @updated_issue.previous_changes["priority_id"][0], value: @updated_issue.previous_changes["priority_id"][1]})
+ end
+
+ # 修改工作项类型
+ if @updated_issue.previous_changes["pm_issue_type"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "pm_issue_type", old_value: @updated_issue.previous_changes["pm_issue_type"][0], value: @updated_issue.previous_changes["pm_issue_type"][1]})
+ end
+
+ # 修改迭代
+ if @updated_issue.previous_changes["pm_sprint_id"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "pm_sprint_id", old_value: @updated_issue.previous_changes["pm_sprint_id"][0], value: @updated_issue.previous_changes["pm_sprint_id"][1]})
+ end
+
+ # 修改代码库
+ if @updated_issue.previous_changes["project_id"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "project_id", old_value: @updated_issue.previous_changes["project_id"][0], value: @updated_issue.previous_changes["project_id"][1]})
+ end
+
+ # 修改里程碑
+ if @updated_issue.previous_changes["fixed_version_id"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "fixed_version_id", old_value: @updated_issue.previous_changes["fixed_version_id"][0], value: @updated_issue.previous_changes["fixed_version_id"][1]})
+ end
+
+ # 更改分支
+ if @updated_issue.previous_changes["branch_name"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "branch_name", old_value: @updated_issue.previous_changes["branch_name"][0], value: @updated_issue.previous_changes["branch_name"][1]})
+ end
+
+ # 更改开始时间
+ if @updated_issue.previous_changes["start_date"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "start_date", old_value: @updated_issue.previous_changes["start_date"][0], value: @updated_issue.previous_changes["start_date"][1]})
+ end
+
+ # 更改结束时间
+ if @updated_issue.previous_changes["due_date"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "due_date", old_value: @updated_issue.previous_changes["due_date"][0], value: @updated_issue.previous_changes["due_date"][1]})
+ end
+
+ # 更改预估工时
+ if @updated_issue.previous_changes["time_scale"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attr", prop_key: "time_scale", old_value: @updated_issue.previous_changes["time_scale"][0], value: @updated_issue.previous_changes["time_scale"][1]})
+ end
+
+ # 更改父工作项
+ if @updated_issue.previous_changes["root_id"].present?
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "root_id", old_value: @updated_issue.previous_changes["root_id"][0], value: @updated_issue.previous_changes["root_id"][1]})
+
+ # 更改子工作项
+ before_parent_issue = Issue.find_by_id(@updated_issue.previous_changes["root_id"][0])
+ if before_parent_issue.present?
+ journal = before_parent_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "tag_leaf_issue", old_value: @updated_issue.id.to_s})
+ end
+
+ after_parent_issue = Issue.find_by_id(@updated_issue.previous_changes["root_id"][1])
+ if after_parent_issue.present?
+ journal = after_parent_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: @updated_issue.pm_issue_type_string, prop_key: "tag_leaf_issue", value: @updated_issue.id.to_s})
+ end
+ end
+ rescue
+ raise Error, "创建操作记录失败!"
+ end
+ end
+
+ def build_assigner_issue_journal_details
+ begin
+ # 更改负责人
+ new_assigner_ids = @assigner_ids
+ new_assigner_ids = [] if @assigner_ids.nil?
+ now_assigner_ids = @updated_issue.assigners.pluck(:id)
+ if !(now_assigner_ids.sort == new_assigner_ids.sort)
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "assigner", prop_key: "#{new_assigner_ids.size}", old_value: now_assigner_ids.join(","), value: new_assigner_ids.join(",")})
+ end
+
+ rescue
+ raise Error, "创建操作记录失败!"
+ end
+ end
+
+ def build_issue_tag_issue_journal_details
+ begin
+ # 更改标记
+ new_issue_tag_ids = @issue_tag_ids
+ new_issue_tag_ids = [] if @issue_tag_ids.nil?
+ now_issue_tag_ids = @updated_issue.issue_tags.pluck(:id)
+ if !(now_issue_tag_ids.sort == new_issue_tag_ids.sort)
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "issue_tag", prop_key: "#{new_issue_tag_ids.size}", old_value: now_issue_tag_ids.join(","), value: new_issue_tag_ids.join(",")})
+ end
+ rescue
+ raise Error, "创建操作记录失败!"
+ end
+ end
+
+
+ def build_attachment_issue_journal_details
+ begin
+ # 更改附件
+ new_attachment_ids = @attachment_ids
+ new_attachment_ids = [] if @attachment_ids.nil?
+ now_attachment_ids = @updated_issue.attachments.pluck(:id)
+ if !(now_attachment_ids.sort == new_attachment_ids.sort)
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
+ journal.journal_details.create!({property: "attachment", prop_key: "#{new_attachment_ids.size}", old_value: now_attachment_ids.join(","), value: new_attachment_ids.join(",")})
+ end
+ rescue
+ raise Error, "创建操作记录失败!"
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/api/pm/sprint_issues/list_service.rb b/app/services/api/pm/sprint_issues/list_service.rb
new file mode 100644
index 000000000..5a539b195
--- /dev/null
+++ b/app/services/api/pm/sprint_issues/list_service.rb
@@ -0,0 +1,65 @@
+class Api::Pm::SprintIssues::ListService < ApplicationService
+
+ include ActiveModel::Model
+
+ attr_reader :category, :pm_project_id, :pm_issue_type, :assigner_id, :priority_id, :status_id, :keyword, :current_user
+ attr_reader :status_ids, :pm_issue_types
+ attr_reader :sort_by, :sort_direction
+ attr_accessor :queried_issues
+
+ validates :category, inclusion: { in: %w[linked unlink], message: '请输入正确的Category'}
+ validates :sort_by, inclusion: { in: %w[issues.status_id issues.created_on issues.updated_on issue_priorities.position] , message: '请输入正确的SortBy'}, allow_blank: true
+ validates :sort_direction, inclusion: { in: %w[asc desc], message: '请输入正确的SortDirection'}, allow_blank: true
+
+ validates :pm_project_id, :current_user, presence: true
+
+ def initialize(params, current_user = nil)
+ @category = params[:category] || "unlink"
+ @pm_project_id = params[:pm_project_id]
+ @pm_issue_type = params[:pm_issue_type]
+ @assigner_id = params[:assigner_id]
+ @priority_id = params[:priority_id]
+ @status_id = params[:status_id]
+ @keyword = params[:keyword]
+ @status_ids = params[:status_ids].present? ? params[:status_ids].split(',') : []
+ @pm_issue_types = params[:pm_issue_types].present? ? params[:pm_issue_types].split(',') : []
+ @sort_by = params[:sort_by].present? ? params[:sort_by] : 'issues.updated_on'
+ @sort_direction = (params[:sort_direction].present? ? params[:sort_direction] : 'desc').downcase
+ @current_user = current_user
+ end
+
+ def call
+ raise Error, errors.full_messages.join(', ') unless valid?
+
+ issue_query_data
+
+ @queried_issues
+ end
+
+ private
+ def issue_query_data
+ issues = @category == "unlink" ? Issue.where(pm_project_id: @pm_project_id, pm_sprint_id: [nil, 0]) : Issue.where(pm_project_id: @pm_project_id).where.not(pm_sprint_id: [nil, 0])
+
+ issues = issues.where(pm_issue_type: @pm_issue_type) if @pm_issue_type.present?
+
+ issues = issues.joins(:assigners).where(users: {id: @assigner_id}) if @assigner_id.present?
+
+ issues = issues.where(priority_id: @priority_id) if @priority_id.present?
+
+ issues = issues.where(status_id: @status_id) if @status_id.present?
+
+ # status_ids
+ issues = issues.where(status_id: @status_ids) unless @status_ids.blank?
+
+ # pm_issue_types
+ issues = issues.where(pm_issue_type: @pm_issue_types) unless @pm_issue_types.blank?
+
+ issues = issues.ransack(subject_cont: @keyword).result if @keyword.present?
+
+ scope = issues.includes(:priority, :issue_status, :user, :show_assigners, :show_issue_tags, :version, :comment_journals)
+ scope = scope.reorder("#{sort_by} #{sort_direction}").distinct
+
+ @queried_issues = scope
+
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/issues/batch_delete_service.rb b/app/services/api/v1/issues/batch_delete_service.rb
index 45821b373..15ebd8d0e 100644
--- a/app/services/api/v1/issues/batch_delete_service.rb
+++ b/app/services/api/v1/issues/batch_delete_service.rb
@@ -19,7 +19,7 @@ class Api::V1::Issues::BatchDeleteService < ApplicationService
project.incre_project_issue_cache_delete_count(@issues.size)
- if Site.has_notice_menu?
+ if Site.has_notice_menu? && !project.id.zero?
@issues.each do |issue|
SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id)
end
diff --git a/app/services/api/v1/issues/batch_update_service.rb b/app/services/api/v1/issues/batch_update_service.rb
index ccf783dca..e826ca190 100644
--- a/app/services/api/v1/issues/batch_update_service.rb
+++ b/app/services/api/v1/issues/batch_update_service.rb
@@ -4,7 +4,7 @@ class Api::V1::Issues::BatchUpdateService < ApplicationService
include Api::V1::Issues::Concerns::Loadable
attr_reader :project, :issues, :params, :current_user
- attr_reader :status_id, :priority_id, :milestone_id
+ attr_reader :status_id, :priority_id, :milestone_id, :project_id
attr_reader :issue_tag_ids, :assigner_ids
validates :project, :issues, :current_user, presence: true
diff --git a/app/services/api/v1/issues/concerns/checkable.rb b/app/services/api/v1/issues/concerns/checkable.rb
index 5eef81584..167c53a6c 100644
--- a/app/services/api/v1/issues/concerns/checkable.rb
+++ b/app/services/api/v1/issues/concerns/checkable.rb
@@ -12,6 +12,10 @@ module Api::V1::Issues::Concerns::Checkable
raise ApplicationService::Error, "Milestone不存在!" unless Version.find_by_id(milestone_id).present?
end
+ def check_root_issue(issue, root_id)
+ raise ApplicationService::Error, "父工作项与当前工作项已存在父子关系!" if Issue.full_children_issues(issue).map{|i| i.id}.include?(root_id)
+ end
+
def check_issue_tags(issue_tag_ids)
raise ApplicationService::Error, "请输入正确的标记ID数组!" unless issue_tag_ids.is_a?(Array)
raise ApplicationService::Error, "最多可选择3个标记" if issue_tag_ids.size > 3
@@ -47,6 +51,7 @@ module Api::V1::Issues::Concerns::Checkable
end
def check_blockchain_token_num(user_id, project_id, blockchain_token_num, now_blockchain_token_num=0)
+ return if project_id.zero?
left_blockchain_token_num = Blockchain::BalanceQueryOneProject.call({"user_id": user_id, "project_id": project_id}) rescue 0
raise ApplicationService::Error, "用户Token不足。" if blockchain_token_num.to_i > (left_blockchain_token_num+now_blockchain_token_num).to_i
end
diff --git a/app/services/api/v1/issues/concerns/loadable.rb b/app/services/api/v1/issues/concerns/loadable.rb
index 547ff50d7..c8f67f256 100644
--- a/app/services/api/v1/issues/concerns/loadable.rb
+++ b/app/services/api/v1/issues/concerns/loadable.rb
@@ -9,7 +9,7 @@ module Api::V1::Issues::Concerns::Loadable
end
def load_attachments(attachment_ids)
- @attachments = Attachment.where("id in (?) or uuid in (?)", attachment_ids, attachment_ids)
+ @attachments = Attachment.where("BINARY id in (?) or uuid in (?)", attachment_ids, attachment_ids)
end
def load_atme_receivers(receivers_login)
diff --git a/app/services/api/v1/issues/create_service.rb b/app/services/api/v1/issues/create_service.rb
index c155b69d4..fdbdc71ed 100644
--- a/app/services/api/v1/issues/create_service.rb
+++ b/app/services/api/v1/issues/create_service.rb
@@ -4,7 +4,7 @@ class Api::V1::Issues::CreateService < ApplicationService
include Api::V1::Issues::Concerns::Loadable
attr_reader :project, :current_user
- attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
+ attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num, :root_subject
attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login
attr_accessor :created_issue
@@ -29,10 +29,17 @@ class Api::V1::Issues::CreateService < ApplicationService
@assigner_ids = params[:assigner_ids]
@attachment_ids = params[:attachment_ids]
@receivers_login = params[:receivers_login]
+ @pm_project_id = params[:pm_project_id]
+ @pm_sprint_id = params[:pm_sprint_id]
+ @pm_issue_type = params[:pm_issue_type]
+ @root_id = params[:root_id]
+ @time_scale = params[:time_scale]
+ @linkable_id = params[:link_able_id]
+ @root_subject = params[:root_subject]
end
- def call
- raise Error, errors.full_messages.join(", ") unless valid?
+ def call
+ raise Error, errors.full_messages.join(', ') unless valid?
ActiveRecord::Base.transaction do
check_issue_status(status_id)
check_issue_priority(priority_id)
@@ -46,7 +53,6 @@ class Api::V1::Issues::CreateService < ApplicationService
load_attachments(attachment_ids) unless attachment_ids.blank?
load_issue_tags(issue_tag_ids) unless issue_tag_ids.blank?
load_atme_receivers(receivers_login) unless receivers_login.blank?
-
try_lock("Api::V1::Issues::CreateService:#{project.id}") # 开始写数据,加锁
@created_issue = Issue.new(issue_attributes)
build_author_participants
@@ -57,39 +63,54 @@ class Api::V1::Issues::CreateService < ApplicationService
@created_issue.assigners = @assigners unless assigner_ids.blank?
@created_issue.attachments = @attachments unless attachment_ids.blank?
@created_issue.issue_tags = @issue_tags unless issue_tag_ids.blank?
-
- @created_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.blank?
+ @created_issue.pm_project_id = @pm_project_id
+ @created_issue.pm_sprint_id = @pm_sprint_id
+ @created_issue.pm_issue_type = @pm_issue_type
+ if @root_subject.present? && @pm_issue_type.to_i == 4
+ @root_issue = Issue.find_by(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id)
+ unless @root_issue.present?
+ @root_issue = Issue.create(subject: @root_subject, pm_issue_type: 4, pm_project_id: @pm_project_id, status_id: 1, priority_id: 1, tracker_id: Tracker.first.id, project_id: @project.id, author_id: current_user.id)
+ end
+ @created_issue.root_id = @root_issue.id
+ else
+ @created_issue.root_id = @root_id
+ end
+ @created_issue.time_scale = @time_scale
+ @created_issue.issue_tags_value = @issue_tags.order('id asc').pluck(:id).join(',') unless issue_tag_ids.blank?
+ @created_issue.changer_id = @current_user.id
@created_issue.save!
- if Site.has_blockchain? && @project.use_blockchain
+ PmLink.create(be_linkable_type: 'Issue', be_linkable_id: @created_issue.id, linkable_type: 'Issue', linkable_id: @linkable_id) if @linkable_id.present?
+ if Site.has_blockchain? && @project.use_blockchain
if @created_issue.blockchain_token_num.present? && @created_issue.blockchain_token_num > 0
Blockchain::CreateIssue.call({user_id: current_user.id, project_id: @created_issue.project_id, token_num: @created_issue.blockchain_token_num})
end
-
- push_activity_2_blockchain("issue_create", @created_issue)
+
+ push_activity_2_blockchain('issue_create', @created_issue)
end
project.del_project_issue_cache_delete_count # 把缓存里存储项目删除issue的个数清除掉
+ unless @project.id.zero?
+ # 新增时向grimoirelab推送事件
+ IssueWebhookJob.set(wait: 5.seconds).perform_later(@created_issue.id)
- # 新增时向grimoirelab推送事件
- IssueWebhookJob.set(wait: 5.seconds).perform_later(@created_issue.id)
+ # @信息发送
+ AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
- # @信息发送
- AtmeService.call(current_user, @atme_receivers, @created_issue) unless receivers_login.blank?
+ # 发消息
+ if Site.has_notice_menu?
+ SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?
+ SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @created_issue&.id)
+ end
- # 发消息
- if Site.has_notice_menu?
- SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @created_issue&.id, assigner_ids) unless assigner_ids.blank?
- SendTemplateMessageJob.perform_later('ProjectIssue', current_user.id, @created_issue&.id)
+ # 触发webhook
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueCreate', @created_issue&.id, current_user.id)
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @created_issue&.id, current_user.id, {issue_tag_ids: [[], issue_tag_ids]}) unless issue_tag_ids.blank?
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @created_issue&.id, current_user.id, {assigner_ids: [[], assigner_ids]}) unless assigner_ids.blank?
end
-
- # 触发webhook
- TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueCreate', @created_issue&.id, current_user.id)
- TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @created_issue&.id, current_user.id, {issue_tag_ids: [[], issue_tag_ids]}) unless issue_tag_ids.blank?
- TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @created_issue&.id, current_user.id, {assigner_ids: [[], assigner_ids]}) unless assigner_ids.blank?
unlock("Api::V1::Issues::CreateService:#{project.id}") # 结束写数据,解锁
end
-
+
return @created_issue
end
@@ -104,8 +125,8 @@ class Api::V1::Issues::CreateService < ApplicationService
status_id: status_id,
priority_id: priority_id,
project_issues_index: (project.get_last_project_issues_index + 1),
- issue_type: "1",
- issue_classify: "issue"
+ issue_type: '1',
+ issue_classify: 'issue'
}
issue_attributes.merge!({description: description}) if description.present?
@@ -119,28 +140,29 @@ class Api::V1::Issues::CreateService < ApplicationService
end
def build_author_participants
- @created_issue.issue_participants.new({participant_type: "authored", participant_id: current_user.id})
+ @created_issue.issue_participants.new({participant_type: 'authored', participant_id: current_user.id})
end
def build_assigner_participants
assigner_ids.each do |aid|
- @created_issue.issue_participants.new({participant_type: "assigned", participant_id: aid})
+ @created_issue.issue_participants.new({participant_type: 'assigned', participant_id: aid})
end
end
def build_atme_participants
@atme_receivers.each do |receiver|
- @created_issue.issue_participants.new({participant_type: "atme", participant_id: receiver.id})
+ @created_issue.issue_participants.new({participant_type: 'atme', participant_id: receiver.id})
end
end
def build_issue_project_trends
- @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: "create"})
+ return if @project.id == 0
+ @created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: 'create'})
@created_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE}) if status_id.to_i == 5
end
def build_issue_journal_details
journal = @created_issue.journals.new({user_id: current_user.id})
- journal.journal_details.new({property: "issue", prop_key: 1, old_value: '', value: ''})
+ journal.journal_details.new({property: 'issue', prop_key: 1, old_value: '', value: ''})
end
end
\ No newline at end of file
diff --git a/app/services/api/v1/issues/delete_service.rb b/app/services/api/v1/issues/delete_service.rb
index b62733181..4d7ff7cfc 100644
--- a/app/services/api/v1/issues/delete_service.rb
+++ b/app/services/api/v1/issues/delete_service.rb
@@ -16,14 +16,16 @@ class Api::V1::Issues::DeleteService < ApplicationService
try_lock("Api::V1::Issues::DeleteService:#{project.id}") # 开始写数据,加锁
delete_issue
+ #删除双向关联
+ PmLink.where(be_linkable_id: @issue.id, be_linkable_type: 'Issue').or(PmLink.where(linkable_id: @issue.id, linkable_type: 'Issue')).map(&:destroy)
project.incre_project_issue_cache_delete_count
- if Site.has_blockchain? && @project.use_blockchain
+ if Site.has_blockchain? && @project.use_blockchain && !project.id.zero?
unlock_balance_on_blockchain(@issue.author_id.to_s, @project.id.to_s, @issue.blockchain_token_num.to_i) if @issue.blockchain_token_num.present?
end
- if Site.has_notice_menu?
+ if Site.has_notice_menu? && !project.id.zero?
SendTemplateMessageJob.perform_later('IssueDeleted', current_user.id, @issue&.subject, @issue.assigners.pluck(:id), @issue.author_id)
end
@@ -37,5 +39,4 @@ class Api::V1::Issues::DeleteService < ApplicationService
def delete_issue
raise Error, "删除疑修失败!" unless issue.destroy!
end
-
end
\ No newline at end of file
diff --git a/app/services/api/v1/issues/list_service.rb b/app/services/api/v1/issues/list_service.rb
index 983e97c18..951c4f463 100644
--- a/app/services/api/v1/issues/list_service.rb
+++ b/app/services/api/v1/issues/list_service.rb
@@ -2,17 +2,18 @@ class Api::V1::Issues::ListService < ApplicationService
include ActiveModel::Model
attr_reader :project, :only_name, :category, :participant_category, :keyword, :author_id, :issue_tag_ids
- attr_reader :begin_date, :end_date
- attr_reader :milestone_id, :assigner_id, :status_id, :sort_by, :sort_direction, :current_user
- attr_accessor :queried_issues, :total_issues_count, :closed_issues_count, :opened_issues_count
+ attr_reader :begin_date, :end_date, :update_begin_date, :update_end_date
+ attr_reader :milestone_id, :assigner_id, :status_id, :priority_id, :sort_by, :sort_direction, :current_user
+ attr_reader :pm_project_id, :pm_project_ids, :pm_sprint_id, :root_id, :pm_issue_type, :status_ids, :ids, :exclude_ids, :pm_issue_types
+ attr_accessor :queried_issues, :total_issues_count, :closed_issues_count, :opened_issues_count, :complete_issues_count, :participator
- validates :category, inclusion: {in: %w(all opened closed), message: '请输入正确的Category'}
- validates :participant_category, inclusion: {in: %w(all aboutme authoredme assignedme atme), message: '请输入正确的ParticipantCategory'}
- validates :sort_by, inclusion: {in: ['issues.created_on', 'issues.updated_on', 'issues.blockchain_token_num', 'issue_priorities.position'], message: '请输入正确的SortBy'}, allow_blank: true
- validates :sort_direction, inclusion: {in: %w(asc desc), message: '请输入正确的SortDirection'}, allow_blank: true
+ validates :category, inclusion: { in: %w[all opened closed], message: '请输入正确的Category'}
+ validates :participant_category, inclusion: { in: %w[all aboutme authoredme assignedme atme], message: '请输入正确的ParticipantCategory'}
+ validates :sort_by, inclusion: { in: %w[issues.created_on issues.updated_on issues.blockchain_token_num issue_priorities.position issues.start_date issues.due_date issues.status_id] , message: '请输入正确的SortBy'}, allow_blank: true
+ validates :sort_direction, inclusion: { in: %w[asc desc], message: '请输入正确的SortDirection'}, allow_blank: true
validates :current_user, presence: true
- def initialize(project, params, current_user=nil)
+ def initialize(project, params, current_user = nil)
@project = project
@only_name = params[:only_name]
@category = params[:category] || 'all'
@@ -22,20 +23,39 @@ class Api::V1::Issues::ListService < ApplicationService
@issue_tag_ids = params[:issue_tag_ids].present? ? params[:issue_tag_ids].split(',') : []
@milestone_id = params[:milestone_id]
@assigner_id = params[:assigner_id]
+ @priority_id = params[:priority_id]
@status_id = params[:status_id]
@begin_date = params[:begin_date]
@end_date = params[:end_date]
+ @update_begin_date = params[:update_begin_date]
+ @update_end_date = params[:update_end_date]
@sort_by = params[:sort_by].present? ? params[:sort_by] : 'issues.updated_on'
+ @pm_project_id = params[:pm_project_id]
+ @pm_project_ids = params[:pm_project_ids]
+ @pm_sprint_id = params[:pm_sprint_id]
+ @root_id = params[:root_id]
+ @pm_issue_type = params[:pm_issue_type]
+ @ids = params[:ids]
+ @exclude_ids = params[:exclude_ids]
+ @status_ids = params[:status_ids].present? ? params[:status_ids].split(',') : []
+ @pm_issue_types = params[:pm_issue_types].present? ? params[:pm_issue_types].split(',') : []
@sort_direction = (params[:sort_direction].present? ? params[:sort_direction] : 'desc').downcase
+ @participator = params[:participator_id].present? ? User.find_by_id(params[:participator_id]) : current_user
@current_user = current_user
end
def call
raise Error, errors.full_messages.join(', ') unless valid?
# begin
- issue_query_data
+ issue_query_data
- return {data: queried_issues, total_issues_count: @total_issues_count, closed_issues_count: @closed_issues_count, opened_issues_count: @opened_issues_count}
+ {
+ data: queried_issues,
+ total_issues_count: @total_issues_count,
+ closed_issues_count: @closed_issues_count,
+ opened_issues_count: @opened_issues_count,
+ complete_issues_count: @complete_issues_count
+ }
# rescue
# raise Error, "服务器错误,请联系系统管理员!"
# end
@@ -43,19 +63,18 @@ class Api::V1::Issues::ListService < ApplicationService
private
def issue_query_data
- issues = @project.issues.issue_issue
-
+ issues = @project&.id.zero? ? Issue.issue_issue : @project.issues.issue_issue
+ @total_issues_count = pm_project_id.present? ? issues.where(pm_issue_type:[1, 2, 3]).count : issues.count
case participant_category
when 'aboutme' # 关于我的
- issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: %w(authored assigned atme), participant_id: current_user&.id})
+ issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: %w[authored assigned atme], participant_id: participator&.id})
when 'authoredme' # 我创建的
- issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'authored', participant_id: current_user&.id})
+ issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'authored', participant_id: participator&.id})
when 'assignedme' # 我负责的
- issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'assigned', participant_id: current_user&.id})
+ issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'assigned', participant_id: participator&.id})
when 'atme' # @我的
- issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'atme', participant_id: current_user&.id})
+ issues = issues.joins(:issue_participants).where(issue_participants: {participant_type: 'atme', participant_id: participator&.id})
end
-
# author_id
issues = issues.where(author_id: author_id) if author_id.present?
@@ -77,6 +96,26 @@ class Api::V1::Issues::ListService < ApplicationService
end
end
+ #pm相关
+ # root_id# -1 查一级目录
+ issues = if root_id.to_i == -1
+ issues.where(root_id: nil)
+ elsif root_id.to_i.positive?
+ issues.where(root_id: root_id)
+ else
+ issues
+ end
+
+ # pm_issue_type
+ issues = issues.where(pm_issue_type: pm_issue_type) if pm_issue_type.present?
+
+ # pm_project_id
+ issues = issues.where(pm_project_id: pm_project_id) if pm_project_id.present?
+ issues = issues.where(pm_project_id: pm_project_ids.to_s.split(",")) if pm_project_ids.present?
+
+ # pm_sprint_id
+ issues = issues.where(pm_sprint_id: pm_sprint_id) if pm_sprint_id.present?
+
# assigner_id
if assigner_id.present?
if assigner_id.to_i == -1
@@ -89,16 +128,36 @@ class Api::V1::Issues::ListService < ApplicationService
# status_id
issues = issues.where(status_id: status_id) if status_id.present? && category != 'closed'
+ # priority_id
+ issues = issues.where(priority_id: priority_id) if priority_id.present?
+
+ # status_ids
+ issues = issues.where(status_id: status_ids) unless status_ids.blank?
+
+ # pm_issue_types
+ issues = issues.where(pm_issue_type: pm_issue_types) unless pm_issue_types.blank?
+
+ # ids
+ issues = issues.where(id: ids.to_s.split(",")) if ids.present?
+
+ # exclude_ids
+ issues = issues.where.not(id: exclude_ids.to_s.split(",")) if exclude_ids.present?
+
if begin_date&.present? || end_date&.present?
issues = issues.where('issues.created_on between ? and ?', begin_date&.present? ? begin_date.to_time : Time.now.beginning_of_day, end_date&.present? ? end_date.to_time.end_of_day : Time.now.end_of_day)
end
+ if update_begin_date&.present? || update_end_date&.present?
+ issues = issues.where('issues.updated_on between ? and ?', update_begin_date&.present? ? update_begin_date.to_time : Time.now.beginning_of_day, update_end_date&.present? ? update_end_date.to_time.end_of_day : Time.now.end_of_day)
+ end
+
# keyword
issues = issues.ransack(id_or_project_issues_index_eq: keyword).result.or(issues.ransack(subject_or_description_cont: keyword).result) if keyword.present?
- @total_issues_count = issues.distinct.size
+
@closed_issues_count = issues.closed.distinct.size
@opened_issues_count = issues.opened.distinct.size
+ @complete_issues_count = issues.closed.distinct.size + issues.where(status_id: 3).distinct.size - issues.where(pm_issue_type: 3, status_id: 3).distinct.size
case category
when 'closed'
diff --git a/app/services/api/v1/issues/update_service.rb b/app/services/api/v1/issues/update_service.rb
index c8050e6da..21f2b9267 100644
--- a/app/services/api/v1/issues/update_service.rb
+++ b/app/services/api/v1/issues/update_service.rb
@@ -3,18 +3,20 @@ class Api::V1::Issues::UpdateService < ApplicationService
include Api::V1::Issues::Concerns::Checkable
include Api::V1::Issues::Concerns::Loadable
- attr_reader :project, :issue, :current_user
+ attr_reader :project, :issue, :current_user, :operate_by
attr_reader :status_id, :priority_id, :milestone_id, :branch_name, :start_date, :due_date, :subject, :description, :blockchain_token_num
- attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login, :before_issue_tag_ids, :before_assigner_ids
+ attr_reader :target_pm_project_id, :pm_sprint_id, :pm_issue_type, :root_id, :time_scale
+ attr_reader :issue_tag_ids, :assigner_ids, :attachment_ids, :receivers_login, :before_issue_tag_ids, :before_assigner_ids, :project_id
attr_accessor :add_assigner_ids, :previous_issue_changes, :updated_issue, :atme_receivers
validates :project, :issue, :current_user, presence: true
validates :blockchain_token_num, numericality: {greater_than: 0}, allow_blank: true
- def initialize(project, issue, params, current_user = nil)
+ def initialize(project, issue, params, current_user = nil, operate_by='Issue')
@project = project
@issue = issue
@current_user = current_user
+ @operate_by = operate_by
@status_id = params[:status_id]
@priority_id = params[:priority_id]
@milestone_id = params[:milestone_id]
@@ -30,6 +32,12 @@ class Api::V1::Issues::UpdateService < ApplicationService
@before_assigner_ids = issue.assigners.pluck(:id)
@attachment_ids = params[:attachment_ids]
@receivers_login = params[:receivers_login]
+ @target_pm_project_id = params[:target_pm_project_id]
+ @pm_sprint_id = params[:pm_sprint_id]
+ @pm_issue_type = params[:pm_issue_type]
+ @root_id = params[:root_id]
+ @time_scale = params[:time_scale]
+ @project_id = params[:project_id]
@add_assigner_ids = []
@previous_issue_changes = {}
end
@@ -40,6 +48,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
check_issue_status(status_id) if status_id.present?
check_issue_priority(priority_id) if priority_id.present?
check_milestone(milestone_id) if milestone_id.present?
+ check_root_issue(issue, root_id) if root_id.present?
check_issue_tags(issue_tag_ids) unless issue_tag_ids.nil?
check_assigners(assigner_ids) unless assigner_ids.nil?
check_attachments(attachment_ids) unless attachment_ids.nil?
@@ -56,7 +65,6 @@ class Api::V1::Issues::UpdateService < ApplicationService
build_assigner_issue_journal_details unless assigner_ids.nil?# 操作记录
build_attachment_issue_journal_details unless attachment_ids.nil?
build_issue_tag_issue_journal_details unless issue_tag_ids.nil?
- build_issue_project_trends if status_id.present? # 开关时间记录
build_assigner_participants unless assigner_ids.nil? # 负责人
build_edit_participants
build_atme_participants if @atme_receivers.present?
@@ -68,28 +76,40 @@ class Api::V1::Issues::UpdateService < ApplicationService
@updated_issue.issue_tags_relates.destroy_all & @updated_issue.issue_tags = @issue_tags || IssueTag.none unless issue_tag_ids.nil?
@updated_issue.issue_tags_value = @issue_tags.order("id asc").pluck(:id).join(",") unless issue_tag_ids.nil?
+ #Pm相关
+ @updated_issue.pm_project_id = @target_pm_project_id unless @target_pm_project_id.nil?
+ @updated_issue.pm_sprint_id = @pm_sprint_id unless @pm_sprint_id.nil?
+ if @updated_issue.children_issues.count == 0 && @updated_issue.parent_id.nil?
+ @updated_issue.pm_issue_type = @pm_issue_type unless @pm_issue_type.nil?
+ end
+ @updated_issue.root_id = @root_id unless @root_id.nil? #不为 nil的时候更新
+ @updated_issue.root_id = nil if @root_id.try(:zero?) #为 0 的时候设置为 nil
+ @updated_issue.time_scale = @time_scale unless @time_scale.nil?
+ @updated_issue.project_id = @project_id unless @project_id.nil?
@updated_issue.updated_on = Time.now
+ @updated_issue.changer_id = @current_user.id
@updated_issue.save!
build_after_issue_journal_details if @updated_issue.previous_changes.present? # 操作记录
build_previous_issue_changes
+ build_issue_project_trends if status_id.present? # 开关时间记录
build_cirle_blockchain_token if blockchain_token_num.present?
-
- # @信息发送
- AtmeService.call(current_user, @atme_receivers, @issue) unless receivers_login.blank?
- # 消息发送
- if Site.has_notice_menu?
- SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_issue_changes) unless previous_issue_changes.blank?
- SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id, add_assigner_ids) unless add_assigner_ids.blank?
+ unless @project.id.zero?
+ # @信息发送
+ AtmeService.call(current_user, @atme_receivers, @issue) unless receivers_login.blank?
+ # 消息发送
+ if Site.has_notice_menu?
+ SendTemplateMessageJob.perform_later('IssueChanged', current_user.id, @issue&.id, previous_issue_changes) unless previous_issue_changes.blank?
+ SendTemplateMessageJob.perform_later('IssueAssigned', current_user.id, @issue&.id, add_assigner_ids) unless add_assigner_ids.blank?
+ end
+ # 触发webhook
+ Rails.logger.info "################### 触发webhook"
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueUpdate', @updated_issue&.id, current_user.id, previous_issue_changes.except(:issue_tags_value, :assigned_to_id))
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @issue&.id, current_user.id, {issue_tag_ids: [before_issue_tag_ids, issue_tag_ids]}) unless issue_tag_ids.nil?
+ TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @issue&.id, current_user.id, {assigner_ids: [before_assigner_ids, assigner_ids]}) unless assigner_ids.nil?
end
unlock("Api::V1::Issues::UpdateService:#{project.id}:#{issue.id}")
- # 触发webhook
- Rails.logger.info "################### 触发webhook"
- TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueUpdate', @updated_issue&.id, current_user.id, previous_issue_changes.except(:issue_tags_value, :assigned_to_id))
- TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueLabel', @issue&.id, current_user.id, {issue_tag_ids: [before_issue_tag_ids, issue_tag_ids]}) unless issue_tag_ids.nil?
- TouchWebhookJob.set(wait: 5.seconds).perform_later('IssueAssign', @issue&.id, current_user.id, {assigner_ids: [before_assigner_ids, assigner_ids]}) unless assigner_ids.nil?
-
return @updated_issue
end
end
@@ -153,7 +173,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
def build_issue_project_trends
if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][1] == 5
- @updated_issue.project_trends.new({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE})
+ @updated_issue.project_trends.create!({user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE})
end
if @updated_issue.previous_changes["status_id"].present? && @updated_issue.previous_changes["status_id"][0] == 5
@updated_issue.project_trends.where(action_type: ProjectTrend::CLOSE).each(&:destroy!)
@@ -164,49 +184,49 @@ class Api::V1::Issues::UpdateService < ApplicationService
begin
# 更改标题
if @updated_issue.previous_changes["subject"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "subject", old_value: @updated_issue.previous_changes["subject"][0], value: @updated_issue.previous_changes["subject"][1]})
end
# 更改描述
if @updated_issue.previous_changes["description"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "description", old_value: @updated_issue.previous_changes["description"][0], value: @updated_issue.previous_changes["description"][1]})
end
# 修改状态
if @updated_issue.previous_changes["status_id"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "status_id", old_value: @updated_issue.previous_changes["status_id"][0], value: @updated_issue.previous_changes["status_id"][1]})
end
# 修改优先级
if @updated_issue.previous_changes["priority_id"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "priority_id", old_value: @updated_issue.previous_changes["priority_id"][0], value: @updated_issue.previous_changes["priority_id"][1]})
end
# 修改里程碑
if @updated_issue.previous_changes["fixed_version_id"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "fixed_version_id", old_value: @updated_issue.previous_changes["fixed_version_id"][0], value: @updated_issue.previous_changes["fixed_version_id"][1]})
end
# 更改分支
if @updated_issue.previous_changes["branch_name"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "branch_name", old_value: @updated_issue.previous_changes["branch_name"][0], value: @updated_issue.previous_changes["branch_name"][1]})
end
# 更改开始时间
if @updated_issue.previous_changes["start_date"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "start_date", old_value: @updated_issue.previous_changes["start_date"][0], value: @updated_issue.previous_changes["start_date"][1]})
end
# 更改结束时间
if @updated_issue.previous_changes["due_date"].present?
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attr", prop_key: "due_date", old_value: @updated_issue.previous_changes["due_date"][0], value: @updated_issue.previous_changes["due_date"][1]})
end
rescue
@@ -221,7 +241,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
new_assigner_ids = [] if @assigner_ids.nil?
now_assigner_ids = @updated_issue.assigners.pluck(:id)
if !(now_assigner_ids & assigner_ids).empty? || !(now_assigner_ids.empty? && new_assigner_ids.empty?)
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "assigner", prop_key: "#{new_assigner_ids.size}", old_value: now_assigner_ids.join(","), value: new_assigner_ids.join(",")})
end
@@ -237,7 +257,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
new_issue_tag_ids = [] if @issue_tag_ids.nil?
now_issue_tag_ids = @updated_issue.issue_tags.pluck(:id)
if !(now_issue_tag_ids & new_issue_tag_ids).empty? || !(now_issue_tag_ids.empty? && new_issue_tag_ids.empty?)
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "issue_tag", prop_key: "#{new_issue_tag_ids.size}", old_value: now_issue_tag_ids.join(","), value: new_issue_tag_ids.join(",")})
end
rescue
@@ -253,7 +273,7 @@ class Api::V1::Issues::UpdateService < ApplicationService
new_attachment_ids = [] if @attachment_ids.nil?
now_attachment_ids = @updated_issue.attachments.pluck(:id)
if !(now_attachment_ids & new_attachment_ids).empty? || !(now_attachment_ids.empty? && new_attachment_ids.empty?)
- journal = @updated_issue.journals.create!({user_id: current_user.id})
+ journal = @updated_issue.journals.create!({user_id: current_user.id, operate_by: @operate_by})
journal.journal_details.create!({property: "attachment", prop_key: "#{new_attachment_ids.size}", old_value: now_attachment_ids.join(","), value: new_attachment_ids.join(",")})
end
rescue
diff --git a/app/services/api/v1/users/delete_user_service.rb b/app/services/api/v1/users/delete_user_service.rb
new file mode 100644
index 000000000..12775eac3
--- /dev/null
+++ b/app/services/api/v1/users/delete_user_service.rb
@@ -0,0 +1,38 @@
+class Api::V1::Users::DeleteUserService < ApplicationService
+ attr_reader :user
+ def initialize(user)
+ @user = user
+ end
+
+ def call
+ begin
+ ActiveRecord::Base.transaction do
+ org_ids = TeamUser.where(user_id: @user.id).pluck(:organization_id) | OrganizationUser.where(user_id: @user.id).pluck(:organization_id)
+ organizations = Organization.where(id: org_ids)
+ organizations.each do |org|
+ # org.team_users.joins(:team).where(user_id: @user.id, teams: {authorize: %w(owner)})
+ owner_count = org.team_users.joins(:team).where(teams: {authorize: %w(owner)}).count
+ # 多个owner时,仅将用户从组织移除, 一个时直接删除
+ org.team_users.where(user_id: @user.id).destroy_all
+ org.organization_users.where(user_id: @user.id, organization_id: org.id).destroy_all
+ if owner_count == 1
+ if org.team_users.joins(:team).where(user_id: @user.id, teams: { authorize: %w(owner) }).count > 0
+ org.destroy!
+ end
+ end
+ end
+ @user.destroy!
+ del_user_data_by_sql(@user.id)
+ Gitea::User::DeleteService.call(@user.login, true)
+ end
+ return true
+ rescue
+ raise Error, "服务器错误,请联系系统管理员!"
+ end
+ end
+
+ def del_user_data_by_sql(user_id)
+ sql1 = "delete from memos where author_id=#{user_id}"
+ ActiveRecord::Base.connection.execute(sql1)
+ end
+end
\ No newline at end of file
diff --git a/app/services/api/v1/users/projects/list_service.rb b/app/services/api/v1/users/projects/list_service.rb
index 079d9d796..e2f2a2f6f 100644
--- a/app/services/api/v1/users/projects/list_service.rb
+++ b/app/services/api/v1/users/projects/list_service.rb
@@ -1,7 +1,7 @@
class Api::V1::Users::Projects::ListService < ApplicationService
include ActiveModel::Model
- attr_reader :observe_user, :category, :is_public, :project_type, :sort_by, :sort_direction, :search, :current_user
+ attr_reader :observe_user, :category, :is_public, :project_type, :sort_by, :sort_direction, :search, :start_at, :end_at, :current_user
attr_accessor :queried_projects
validates :category, inclusion: {in: %w(all join created manage watched forked), message: "请输入正确的Category"}
@@ -18,6 +18,8 @@ class Api::V1::Users::Projects::ListService < ApplicationService
@sort_by = params[:sort_by] || 'updated_on'
@sort_direction = params[:sort_direction] || 'desc'
@search = params[:search]
+ @start_at = params[:start_at]
+ @end_at = params[:end_at]
@current_user = current_user
end
@@ -53,10 +55,16 @@ class Api::V1::Users::Projects::ListService < ApplicationService
projects = Project.from("( #{normal_projects} UNION #{org_projects} ) AS projects").distinct
when 'watched'
projects = projects.where.not(user_id: observe_user.id).joins(:watchers).where(watchers: {watchable_type: "Project", user_id: observe_user.id})
+ projects = projects.joins(:watchers).where("watchers.created_at > ? and watchers.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
when 'only_watched'
projects = projects.where.joins(:watchers).where(watchers: {watchable_type: "Project", user_id: observe_user.id})
+ projects = projects.joins(:watchers).where("watchers.created_at > ? and watchers.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
when 'forked'
- fork_ids = observe_user.fork_users.select(:id, :fork_project_id).pluck(:fork_project_id)
+ if start_at.present? && end_at.present?
+ fork_ids = observe_user.fork_users.where("created_at > ? and created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)).select(:id, :fork_project_id).pluck(:fork_project_id)
+ else
+ fork_ids = observe_user.fork_users.select(:id, :fork_project_id).pluck(:fork_project_id)
+ end
projects = projects.where(id: fork_ids)
else
normal_projects = projects.members_projects(observe_user.id).to_sql
diff --git a/app/services/api/v1/users/update_email_service.rb b/app/services/api/v1/users/update_email_service.rb
index 7ed5a0fbc..3f0875d8a 100644
--- a/app/services/api/v1/users/update_email_service.rb
+++ b/app/services/api/v1/users/update_email_service.rb
@@ -1,5 +1,6 @@
class Api::V1::Users::UpdateEmailService < ApplicationService
include ActiveModel::Model
+ include AesCryptHelper
attr_reader :user, :token, :password, :mail, :old_mail, :code, :verify_code
attr_accessor :gitea_data
@@ -10,7 +11,7 @@ class Api::V1::Users::UpdateEmailService < ApplicationService
def initialize(user, params, token =nil)
@user = user
@token = token
- @password = params[:password]
+ @password = decrypt(params[:password]) rescue ""
@mail = params[:email]
@old_mail = user.mail
@code = params[:code]
diff --git a/app/services/api/v1/users/update_phone_service.rb b/app/services/api/v1/users/update_phone_service.rb
index ed53d7eb5..b79387773 100644
--- a/app/services/api/v1/users/update_phone_service.rb
+++ b/app/services/api/v1/users/update_phone_service.rb
@@ -1,5 +1,6 @@
class Api::V1::Users::UpdatePhoneService < ApplicationService
include ActiveModel::Model
+ include AesCryptHelper
attr_reader :user, :password, :phone, :code, :verify_code
@@ -8,7 +9,7 @@ class Api::V1::Users::UpdatePhoneService < ApplicationService
def initialize(user, params)
@user = user
- @password = params[:password]
+ @password = decrypt(params[:password]) rescue ""
@phone = params[:phone]
@code = params[:code]
@verify_code = VerificationCode.where(phone: @phone, code_type: 4).last
diff --git a/app/services/cache/v2/project_common_service.rb b/app/services/cache/v2/project_common_service.rb
index 1e85f3e08..98a19a80d 100644
--- a/app/services/cache/v2/project_common_service.rb
+++ b/app/services/cache/v2/project_common_service.rb
@@ -102,7 +102,7 @@ class Cache::V2::ProjectCommonService < ApplicationService
return
else
load_project
- return unless @project.is_full_public
+ return unless @project.present? && @project.is_full_public
if @owner_id.present?
if $redis_cache.hget(project_common_key, owner_id_key).nil?
reset_project_owner_id
diff --git a/app/services/cache/v2/user_date_rank_service.rb b/app/services/cache/v2/user_date_rank_service.rb
index b669e4d67..d390d1f5f 100644
--- a/app/services/cache/v2/user_date_rank_service.rb
+++ b/app/services/cache/v2/user_date_rank_service.rb
@@ -87,14 +87,22 @@ class Cache::V2::UserDateRankService < ApplicationService
def set_user_rank
set_user_statistic
follow_count = $redis_cache.hget(user_date_statistic_key, "follow-count") || 0
+ follow_count = follow_count.to_i < 0 ? 0 : follow_count
pullrequest_count = $redis_cache.hget(user_date_statistic_key, "pullrequest-count") || 0
+ pullrequest_count = pullrequest_count.to_i < 0 ? 0 : pullrequest_count
issues_count = $redis_cache.hget(user_date_statistic_key, "issue-count") || 0
+ issues_count = issues_count.to_i < 0 ? 0 : issues_count
project_count = $redis_cache.hget(user_date_statistic_key, "project-count") || 0
+ project_count = project_count.to_i < 0 ? 0 : project_count
fork_count = $redis_cache.hget(user_date_statistic_key, "fork-count") || 0
+ fork_count = fork_count.to_i < 0 ? 0 : fork_count
project_watchers_count = $redis_cache.hget(user_date_statistic_key, "project-watcher-count") || 0
+ project_watchers_count = project_watchers_count.to_i < 0 ? 0 : project_watchers_count
project_praises_count = $redis_cache.hget(user_date_statistic_key, "project-praise-count") || 0
+ project_praises_count = project_praises_count.to_i < 0 ? 0 : project_praises_count
project_language = $redis_cache.hget(user_date_statistic_key, "project-language")
project_languages_count = project_language.nil? || project_language == "{}" ? 0 : JSON.parse(project_language).length
+ project_languages_count = project_languages_count.to_i < 0 ? 0 : project_languages_count
# 影响力
influence = (60.0 + follow_count.to_i / (follow_count.to_i + 20.0) * 40.0).to_i
diff --git a/app/services/gitea/accelerator/migrate_service.rb b/app/services/gitea/accelerator/migrate_service.rb
index 86b8b3db1..5de97fe70 100644
--- a/app/services/gitea/accelerator/migrate_service.rb
+++ b/app/services/gitea/accelerator/migrate_service.rb
@@ -45,6 +45,7 @@ class Gitea::Accelerator::MigrateService < Gitea::Accelerator::BaseService
repo_name: params[:repository_name],
auth_username: params[:auth_username],
auth_password: Base64.decode64(params[:auth_password]),
+ auth_token: params[:auth_token],
mirror: ActiveModel::Type::Boolean.new.cast(params[:is_mirror])
}
end
diff --git a/app/services/gitea/repository/action_secrets_service.rb b/app/services/gitea/repository/action_secrets_service.rb
new file mode 100644
index 000000000..d5e782073
--- /dev/null
+++ b/app/services/gitea/repository/action_secrets_service.rb
@@ -0,0 +1,33 @@
+class Gitea::Repository::ActionSecretsService < Gitea::ClientService
+ attr_reader :owner, :repo, :secret_name, :secret
+
+ def initialize(owner, repo, secret_name, secret)
+ @owner = owner
+ @repo = repo
+ @secret_name = secret_name
+ @secret = secret
+ end
+
+ def call
+ response = put(url, request_params)
+ render_201_response(response)
+ end
+
+ def destroy
+ response = delete(url, request_params)
+ render_201_response(response)
+ end
+
+
+ private
+
+ def request_params
+ Hash.new.merge(token: owner.gitea_token, data: { data: secret } )
+ end
+
+
+
+ def url
+ "/repos/#{owner.login}/#{repo}/actions/secrets/#{secret_name}".freeze
+ end
+end
diff --git a/app/services/gitea/repository/commits/list_service.rb b/app/services/gitea/repository/commits/list_service.rb
index 700508539..c4bd89b15 100644
--- a/app/services/gitea/repository/commits/list_service.rb
+++ b/app/services/gitea/repository/commits/list_service.rb
@@ -19,7 +19,7 @@ class Gitea::Repository::Commits::ListService < Gitea::ClientService
private
def params
- { sha: args[:sha] || 'master', page: args[:page] || PAGINATE_DEFAULT_PAGE, limit: args[:limit] || PAGINATE_DEFAULT_LIMIT, token: args[:token] || "", stat: args[:page].to_i != 1 && args[:limit] !=1 }
+ { sha: args[:sha] || 'master', page: args[:page] || PAGINATE_DEFAULT_PAGE, limit: args[:limit] || PAGINATE_DEFAULT_LIMIT, token: args[:token] || "", stat: args[:page].to_i != 1 && args[:limit] !=1, files: false }
end
def url
diff --git a/app/services/gitea/user/delete_service.rb b/app/services/gitea/user/delete_service.rb
index 9011158d2..ce357e3d5 100644
--- a/app/services/gitea/user/delete_service.rb
+++ b/app/services/gitea/user/delete_service.rb
@@ -1,8 +1,9 @@
class Gitea::User::DeleteService < Gitea::ClientService
- attr_reader :username
+ attr_reader :username, :purge
- def initialize(username)
+ def initialize(username, purge = false)
@username = username
+ @purge = purge
end
def call
@@ -20,7 +21,7 @@ class Gitea::User::DeleteService < Gitea::ClientService
end
def request_url
- "/admin/users/#{username}"
+ @purge ? "/admin/users/#{username}?purge=true" : "/admin/users/#{username}"
end
def params
diff --git a/app/services/info_risk_control_service.rb b/app/services/info_risk_control_service.rb
index f8bd993b5..96a677806 100644
--- a/app/services/info_risk_control_service.rb
+++ b/app/services/info_risk_control_service.rb
@@ -39,14 +39,14 @@ class InfoRiskControlService < ApplicationService
def remote_ip_risk_control
result = Rails.cache.read("InfoRiskControlService-RemoteIp-#{remote_ip}")
if result.present?
- if result.to_i > 20
+ if result.to_i > 30
@status = 0
@message = "暂时无法请求,请稍后再试"
else
Rails.cache.write("InfoRiskControlService-RemoteIp-#{remote_ip}", result.to_i + 1)
end
else
- Rails.cache.write("InfoRiskControlService-RemoteIp-#{remote_ip}", 1, expires_in: 1.day)
+ Rails.cache.write("InfoRiskControlService-RemoteIp-#{remote_ip}", 1, expires_in: 2.hours)
end
end
diff --git a/app/services/projects/migrate_service.rb b/app/services/projects/migrate_service.rb
index f157ada1d..d81e24189 100644
--- a/app/services/projects/migrate_service.rb
+++ b/app/services/projects/migrate_service.rb
@@ -55,6 +55,7 @@ class Projects::MigrateService < ApplicationService
login: params[:auth_username],
password: params[:auth_password],
auth_token: params[:auth_token],
+ service: params[:service],
is_mirror: params[:is_mirror],
source_clone_url: params[:source_clone_url]
}
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index b83ec51d2..973d4ede3 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -10,6 +10,7 @@ class Projects::TransferService < ApplicationService
def call
Rails.logger.info("###### Project transfer_service begin ######")
ActiveRecord::Base.transaction do
+ update_actions
gitea_update_owner
update_owner
update_repo_url
@@ -30,6 +31,17 @@ class Projects::TransferService < ApplicationService
project.members.map{|m| m.destroy! if m.user_id == owner.id || project.member(new_owner.id) || (new_owner.is_a?(Organization) && new_owner.is_member?(m.user_id)) }
project.update!(user_id: new_owner.id)
project.set_owner_permission(new_owner)
+ project.pinned_projects.destroy_all # 移除原来精选的项目
+ end
+
+ def update_actions
+ begin
+ action_params = { has_actions: false }
+ Gitea::Repository::UpdateService.call(owner, project.identifier, action_params)
+ project.update action_params
+ rescue Exception => e
+ Rails.logger.info("##### Project transfer_service, gitea transfer error #{e}")
+ end
end
def update_repo_url
diff --git a/app/services/projects/verify_auth_token_service.rb b/app/services/projects/verify_auth_token_service.rb
index 36dcb106f..3e4cc64e5 100644
--- a/app/services/projects/verify_auth_token_service.rb
+++ b/app/services/projects/verify_auth_token_service.rb
@@ -21,7 +21,7 @@ class Projects::VerifyAuthTokenService < ApplicationService
private
def regular_url
regx = /\/\/[\s\S]*.git$/ #获取字串
- data = (regx.match @url).to_s[2..-5].split("/")
+ data = (regx.match @url).to_s[2..-5].to_s.split("/")
@website = data[0]
@owner = data[1]
@repo = data[2]
@@ -36,16 +36,66 @@ class Projects::VerifyAuthTokenService < ApplicationService
gitlab_verify
when "gitee.com"
gitee_verify
+ when "gitlink.org.cn"
+ gitlink_verify
+ when "gitea.com"
+ gitea_verify
+ when "gitcode.com"
+ gitcode_verify
+ else
+ @success = nil
end
end
+ def gitcode_verify
+ url = "/api/v5/user"
+ api_url = "https://api.gitcode.com"
+ client = Faraday.new(url: api_url)
+ client.options["open_timeout"] = 100
+ client.options["timeout"] = 100
+ client.options["write_timeout"] = 100
+ req_params={
+ access_token: @token
+ }
+ response = client.public_send("get", url, req_params)
+ @success = true if response.status == 200
+ end
+
+ def gitea_verify
+ url = "/api/v1/user"
+ api_url = "https://gitea.com"
+ client = Faraday.new(url: api_url)
+ client.options["open_timeout"] = 100
+ client.options["timeout"] = 100
+ client.options["write_timeout"] = 100
+ req_params={
+ token: @token
+ }
+ response = client.public_send("get", url, req_params)
+ @success = true if response.status == 200
+ end
+
+ def gitlink_verify
+ url = "/api/v1/user"
+ api_url = "https://cdn05042023.gitlink.org.cn"
+ client = Faraday.new(url: api_url)
+ client.options["open_timeout"] = 100
+ client.options["timeout"] = 100
+ client.options["write_timeout"] = 100
+ req_params={
+ token: @token
+ }
+ response = client.public_send("get", url, req_params)
+ @success = true if response.status == 200
+ end
+
def gitee_verify
url = "/api/v5/repos/#{@owner}/#{@repo}"
api_url= "https://gitee.com"
client = Faraday.new(url: api_url)
- client.options["open_timeout"] = 1
- client.options["timeout"] = 1
- client.options["write_timeout"] = 1
+ client.options["open_timeout"] = 100
+ client.options["timeout"] = 100
+ client.options["write_timeout"] = 100
req_params={
access_token: @token,
owner: @owner,
@@ -59,9 +109,9 @@ class Projects::VerifyAuthTokenService < ApplicationService
url = "/octocat"
api_url= "https://api.github.com"
client = Faraday.new(url: api_url)
- client.options["open_timeout"] = 1
- client.options["timeout"] = 1
- client.options["write_timeout"] = 1
+ client.options["open_timeout"] = 100
+ client.options["timeout"] = 100
+ client.options["write_timeout"] = 100
client.headers["Authorization"] = "Bearer #{@token}"
response = client.public_send("get", url)
@success = true if response.status == 200
@@ -71,9 +121,9 @@ class Projects::VerifyAuthTokenService < ApplicationService
url = "/api/v4/projects"
api_url= "https://gitlab.com"
client = Faraday.new(url: api_url)
- client.options["open_timeout"] = 1
- client.options["timeout"] = 1
- client.options["write_timeout"] = 1
+ client.options["open_timeout"] = 100
+ client.options["timeout"] = 100
+ client.options["write_timeout"] = 100
req_params={
private_token: @token
}
diff --git a/app/services/repositories/migrate_service.rb b/app/services/repositories/migrate_service.rb
index deb0e30d3..4c3d4668d 100644
--- a/app/services/repositories/migrate_service.rb
+++ b/app/services/repositories/migrate_service.rb
@@ -21,7 +21,7 @@ class Repositories::MigrateService < ApplicationService
private
def repository_params
- params.merge(project_id: project.id)
+ params.except(:service).merge(project_id: project.id)
end
def gitea_repository_params
@@ -33,7 +33,8 @@ class Repositories::MigrateService < ApplicationService
mirror: wrapper_mirror || false,
auth_username: params[:login],
auth_password: Base64.decode64(params[:password] || ""),
- auth_token: params[:auth_token]
+ auth_token: params[:auth_token],
+ service: params[:service] || 'git',
}
end
diff --git a/app/services/users/project_service.rb b/app/services/users/project_service.rb
index f08b96009..86b2e9bb2 100644
--- a/app/services/users/project_service.rb
+++ b/app/services/users/project_service.rb
@@ -20,6 +20,7 @@ class Users::ProjectService
projects = category_filter(projects)
projects = status_filter(projects)
+ projects = by_project_topic(projects)
custom_sort(projects, params[:sort_by], params[:sort_direction])
end
@@ -45,6 +46,14 @@ class Users::ProjectService
end
end
+ def by_project_topic(items)
+ if params[:topic_name].present?
+ items.with_project_topic_name(params[:topic_name].to_s.split(","))
+ else
+ items.with_project_topic(params[:topic_id])
+ end
+ end
+
def self_or_admin?
User.current.id == user.id || User.current.admin?
end
diff --git a/app/services/users/register_service.rb b/app/services/users/register_service.rb
index fc0e4231e..f6c8c2cf2 100644
--- a/app/services/users/register_service.rb
+++ b/app/services/users/register_service.rb
@@ -1,8 +1,10 @@
class Users::RegisterService < ApplicationService
+ include AesCryptHelper
+
def initialize(params)
@login = params[:login]
@namespace = params[:namespace]
- @password = params[:password]
+ @password = decrypt(params[:password]) rescue ""
@code = params[:code]
end
diff --git a/app/views/action/node_inputs/edit.html.erb b/app/views/action/node_inputs/edit.html.erb
index 6ae9f8a78..6139eb73f 100644
--- a/app/views/action/node_inputs/edit.html.erb
+++ b/app/views/action/node_inputs/edit.html.erb
@@ -39,7 +39,7 @@
说明:该界面适用于action 节点配置参数配置
+ID | 节点名称 | +节点标识 | 节点全称 | 节点描述 | 分类 | @@ -27,6 +37,7 @@ <% @nodes.each do |info| %>
---|---|---|---|---|---|
<%= info.id %> | +<%= info.label %> | <%= info.name %> | <%= info.full_name %> | <%= info.description %> | diff --git a/app/views/action/nodes/index.json.jbuilder b/app/views/action/nodes/index.json.jbuilder index 3909639ce..835c7f145 100644 --- a/app/views/action/nodes/index.json.jbuilder +++ b/app/views/action/nodes/index.json.jbuilder @@ -2,7 +2,7 @@ json.types @node_types.each do |node_type| if node_type.name.to_s == "未分类" json.extract! node_type, :id, :name json.nodes @no_type_nodes do |node| - json.extract! node, :id, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count + json.extract! node, :id, :label, :name, :full_name, :description, :icon, :action_node_types_id, :yaml, :sort_no, :use_count, :node_type, :is_mutil_link, :link_type json.inputs node.action_node_inputs do |node_input| json.partial! "node_input", locals: { node_input: node_input, node: node } end @@ -10,7 +10,7 @@ json.types @node_types.each do |node_type| else json.extract! node_type, :id, :name json.nodes node_type.action_nodes do |node| - json.extract! node, :id, :name, :full_name, :description, :action_node_types_id, :yaml, :sort_no, :use_count + json.extract! node, :id, :label, :name, :full_name, :description, :icon, :action_node_types_id, :yaml, :sort_no, :use_count, :node_type, :is_mutil_link, :link_type json.inputs node.action_node_inputs do |node_input| json.partial! "node_input", locals: { node_input: node_input, node: node } end diff --git a/app/views/action/nodes/show.json.jbuilder b/app/views/action/nodes/show.json.jbuilder index 64548544d..e731dbd0b 100644 --- a/app/views/action/nodes/show.json.jbuilder +++ b/app/views/action/nodes/show.json.jbuilder @@ -1,7 +1,7 @@ json.status 0 json.message "success" -json.extract! @node, :id, :name, :full_name, :description, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no, :use_count +json.extract! @node, :id, :name, :full_name, :description, :icon, :action_node_types_id, :is_local, :local_url, :yaml, :sort_no, :use_count, :label, :node_type, :is_mutil_link, :link_type json.inputs @node.action_node_inputs do |node_input| json.partial! "node_input", locals: { node_input: node_input, node: @node } end \ No newline at end of file diff --git a/app/views/admins/dashboards/index.html.erb b/app/views/admins/dashboards/index.html.erb index 5441a1802..a55ef55e4 100644 --- a/app/views/admins/dashboards/index.html.erb +++ b/app/views/admins/dashboards/index.html.erb @@ -85,7 +85,9 @@